1/*
2 * Implementation of support type for informal protocols.
3 *
4 * See the module DOCSTR for more information.
5 */
6#include "pyobjc.h"
7
8PyDoc_STRVAR(proto_cls_doc,
9"objc.informal_protocol(name, selector_list)\n"
10"\n"
11"This class can be used to specify which methods are supported by an informal\n"
12"protocol. Instances of this type can by used while creating subclasses of \n"
13"objective-C classes to automaticly specify method signatures et.al."
14"");
15
16typedef struct {
17	PyObject_HEAD
18
19	PyObject* name;
20	PyObject* selectors;
21} PyObjCInformalProtocol;
22
23
24static PyObject* selToProtocolMapping = NULL;
25
26
27static void
28proto_dealloc(PyObject* object)
29{
30	PyObjCInformalProtocol* self = (PyObjCInformalProtocol*)object;
31#if 0
32	/*
33	 * For some reason this code causes a crash, while it should
34	 * be the reverse of the code in proto_new.
35	 */
36	Py_ssize_t len = PyTuple_Size(self->selectors);
37	Py_ssize_t i;
38
39	for (i = 0; i < len; i++) {
40		PyObjCSelector* tmp =
41			(PyObjCSelector*)PyTuple_GET_ITEM(
42				self->selectors, i);
43
44		PyDict_DelItemString(selToProtocolMapping,
45			sel_getName(tmp->sel_selector));
46	}
47#endif
48
49	Py_XDECREF(self->name);
50	Py_XDECREF(self->selectors);
51	Py_TYPE(object)->tp_free(object);
52}
53
54static PyObject*
55proto_repr(PyObject* object)
56{
57	PyObjCInformalProtocol* self = (PyObjCInformalProtocol*)object;
58	PyObject* b = NULL;
59
60	if (PyUnicode_Check(self->name)) {
61		b = PyUnicode_AsEncodedString(self->name, NULL, NULL);
62#if PY_MAJOR_VERSION == 2
63	} else if (PyString_Check(self->name)) {
64		b = self->name; Py_INCREF(b);
65#endif
66	} else {
67		b = PyBytes_FromString("<null>");
68	}
69	if (b == NULL) {
70		return NULL;
71	}
72
73	PyObject* r = PyText_FromFormat("<%s %s at %p>", Py_TYPE(self)->tp_name, PyBytes_AsString(b), (void*)self);
74	Py_XDECREF(b);
75	return r;
76}
77
78static PyObject*
79proto_new(PyTypeObject* type __attribute__((__unused__)),
80	PyObject* args, PyObject* kwds)
81{
82static	char*	keywords[] = { "name", "selectors", NULL };
83	PyObjCInformalProtocol* result;
84	PyObject* name;
85	PyObject* selectors;
86	Py_ssize_t       i, len;
87
88	if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO:informal_protocol",
89			keywords, &name, &selectors)) {
90		return NULL;
91	}
92
93	if (PyUnicode_Check(name)) {
94		/* pass */
95#if PY_MAJOR_VERSION == 2
96	} else if (PyString_Check(name)) {
97		/* pass */
98#endif
99	} else {
100		PyErr_SetString(PyExc_TypeError,
101			"Name must be a string");
102		return NULL;
103	}
104
105	selectors = PySequence_Tuple(selectors);
106	if (selectors == NULL) {
107		return NULL;
108	}
109
110	result = (PyObjCInformalProtocol*)PyObject_New(
111			PyObjCInformalProtocol, &PyObjCInformalProtocol_Type);
112
113	result->name = name;
114	Py_XINCREF(name);
115	result->selectors = selectors;
116
117	len = PyTuple_GET_SIZE(selectors);
118	for (i = 0; i < len; i++) {
119		if (!PyObjCSelector_Check(
120				PyTuple_GET_ITEM(selectors, i))) {
121			PyErr_Format(PyExc_TypeError,
122				"Item %"PY_FORMAT_SIZE_T"d is not a selector", i);
123			Py_DECREF(result);
124			return NULL;
125		}
126	}
127
128	if (selToProtocolMapping == NULL) {
129		selToProtocolMapping = PyDict_New();
130		if (selToProtocolMapping == NULL) {
131			Py_DECREF(result);
132			return NULL;
133		}
134	}
135
136	for (i = 0; i < len; i++) {
137		PyObjCSelector* tmp =
138			(PyObjCSelector*)PyTuple_GET_ITEM(selectors, i);
139
140		PyDict_SetItemString(selToProtocolMapping,
141			(char*)sel_getName(tmp->sel_selector),
142			(PyObject*)result);
143	}
144
145
146	return (PyObject*)result;
147}
148
149static int
150proto_traverse(PyObject* self, visitproc visit, void* handle)
151{
152	PyObjCInformalProtocol* me = (PyObjCInformalProtocol*)self;
153	int                   err;
154
155	err = visit(me->name, handle);
156	if (err) return err;
157	err = visit(me->selectors, handle);
158	if (err) return err;
159	return 0;
160}
161
162static PyMemberDef proto_members[] = {
163	{
164		"__name__",
165		T_OBJECT,
166		offsetof(PyObjCInformalProtocol, name),
167		READONLY,
168		NULL
169	},
170	{
171		"selectors",
172		T_OBJECT,
173		offsetof(PyObjCInformalProtocol, selectors),
174		READONLY,
175		NULL
176	},
177
178	{ 0, 0, 0, 0, 0 }
179};
180
181PyTypeObject PyObjCInformalProtocol_Type = {
182	PyVarObject_HEAD_INIT(&PyType_Type, 0)
183	"objc.informal_protocol",		/* tp_name */
184	sizeof(PyObjCInformalProtocol),		/* tp_basicsize */
185	0,					/* tp_itemsize */
186	/* methods */
187	proto_dealloc,	 			/* tp_dealloc */
188	0,					/* tp_print */
189	0,					/* tp_getattr */
190	0,					/* tp_setattr */
191	0,					/* tp_compare */
192	proto_repr,				/* tp_repr */
193	0,					/* tp_as_number */
194	0,					/* tp_as_sequence */
195	0,		       			/* tp_as_mapping */
196	0,					/* tp_hash */
197	0,					/* tp_call */
198	0,					/* tp_str */
199	PyObject_GenericGetAttr,		/* tp_getattro */
200	0,					/* tp_setattro */
201	0,					/* tp_as_buffer */
202	Py_TPFLAGS_DEFAULT,			/* tp_flags */
203 	proto_cls_doc,				/* tp_doc */
204 	proto_traverse,				/* tp_traverse */
205 	0,					/* tp_clear */
206	0,					/* tp_richcompare */
207	0,					/* tp_weaklistoffset */
208	0,					/* tp_iter */
209	0,					/* tp_iternext */
210	0,					/* tp_methods */
211	proto_members,				/* tp_members */
212	0, /* proto_getset , */			/* tp_getset */
213	0,					/* tp_base */
214	0,					/* tp_dict */
215	0,					/* tp_descr_get */
216	0,					/* tp_descr_set */
217	0,					/* tp_dictoffset */
218	0,					/* tp_init */
219	0,					/* tp_alloc */
220	proto_new,				/* tp_new */
221	0,		        		/* tp_free */
222	0,					/* tp_is_gc */
223        0,                                      /* tp_bases */
224        0,                                      /* tp_mro */
225        0,                                      /* tp_cache */
226        0,                                      /* tp_subclasses */
227        0,                                      /* tp_weaklist */
228        0                                       /* tp_del */
229#if PY_VERSION_HEX >= 0x02060000
230	, 0                                     /* tp_version_tag */
231#endif
232
233};
234
235
236/*
237 * Find information about a selector in the protocol.
238 *
239 * Return NULL if no information can be found, but does not set an
240 * exception.
241 */
242PyObject*
243PyObjCInformalProtocol_FindSelector(PyObject* obj, SEL selector, int isClassMethod)
244{
245	PyObjCInformalProtocol* self = (PyObjCInformalProtocol*)obj;
246	Py_ssize_t i, len;
247	PyObject* cur;
248	PyObject* seq;
249
250	if (!PyObjCInformalProtocol_Check(obj)) {
251		PyErr_Format(PyExc_TypeError,
252			"First argument is not an 'objc.informal_protocol' "
253			"but '%s'", Py_TYPE(obj)->tp_name);
254		return 0;
255	}
256
257	seq = PySequence_Fast(self->selectors,"selector list not a sequence?");
258	if (seq == NULL) {
259		return 0;
260	}
261
262	len = PySequence_Fast_GET_SIZE(seq);
263	for (i = 0; i < len; i++) {
264		cur = PySequence_Fast_GET_ITEM(self->selectors, i);
265		if (cur == NULL) {
266			continue;
267		}
268
269		if (PyObjCSelector_Check(cur)) {
270			int class_sel = (
271				PyObjCSelector_GetFlags(cur)
272				& PyObjCSelector_kCLASS_METHOD) != 0;
273			if ((isClassMethod && !class_sel)
274					|| (!isClassMethod && class_sel)) {
275				continue;
276			}
277
278			if (sel_isEqual(PyObjCSelector_GetSelector(cur), selector)) {
279				Py_DECREF(seq);
280				return cur;
281			}
282		}
283	}
284	Py_DECREF(seq);
285	return NULL;
286}
287
288PyObject*
289findSelInDict(PyObject* clsdict, SEL selector)
290{
291	PyObject* values;
292	PyObject* seq;
293	Py_ssize_t       i, len;
294
295	values = PyDict_Values(clsdict);
296	if (values == NULL) {
297		return NULL;
298	}
299
300	seq = PySequence_Fast(values, "PyDict_Values result not a sequence");
301	if (seq == NULL) {
302		return NULL;
303	}
304
305	len = PySequence_Fast_GET_SIZE(seq);
306	for (i = 0; i < len; i++) {
307		PyObject* v = PySequence_Fast_GET_ITEM(seq, i);
308		if (!PyObjCSelector_Check(v)) continue;
309		if (PyObjCSelector_GetSelector(v) == selector) {
310			Py_DECREF(seq);
311			Py_DECREF(values);
312			Py_INCREF(v);
313			return v;
314		}
315	}
316	Py_DECREF(seq);
317	Py_DECREF(values);
318	return NULL;
319}
320
321int
322signaturesEqual(const char* sig1, const char* sig2)
323{
324	char buf1[1024];
325	char buf2[1024];
326	int r;
327
328	/* Return 0 if the two signatures are not equal */
329	if (strcmp(sig1, sig2) == 0) return 1;
330
331	/* For some reason compiler-generated signatures contain numbers that
332	 * are not used by the runtime. These are irrelevant for our comparison
333	 */
334	r = PyObjCRT_SimplifySignature(sig1, buf1, sizeof(buf1));
335	if (r == -1) {
336		return 0;
337	}
338
339	r = PyObjCRT_SimplifySignature(sig2, buf2, sizeof(buf2));
340	if (r == -1) {
341		return 0;
342	}
343	return strcmp(buf1, buf2) == 0;
344}
345
346/*
347 * Verify that 'cls' conforms to the informal protocol
348 */
349int
350PyObjCInformalProtocol_CheckClass(
351	PyObject* obj, char* name, PyObject* super_class, PyObject* clsdict)
352{
353	PyObjCInformalProtocol* self = (PyObjCInformalProtocol*)obj;
354	Py_ssize_t i, len;
355	PyObject* cur;
356	PyObject* seq;
357
358	if (!PyObjCInformalProtocol_Check(obj)) {
359		PyErr_Format(PyExc_TypeError,
360			"First argument is not an 'objc.informal_protocol' "
361			"but '%s'", Py_TYPE(obj)->tp_name);
362		return 0;
363	}
364	if (!PyObjCClass_Check(super_class)) {
365		PyErr_Format(PyExc_TypeError,
366			"Third argument is not an 'objc.objc_class' but "
367			"'%s'", Py_TYPE(super_class)->tp_name);
368		return 0;
369	}
370	if (!PyDict_Check(clsdict)) {
371		PyErr_Format(PyExc_TypeError,
372			"Fourth argument is not a 'dict' but '%s'",
373			Py_TYPE(clsdict)->tp_name);
374		return 0;
375	}
376
377	seq = PySequence_Fast(self->selectors, "selector list not a sequence");
378	if (seq == NULL) {
379		return 0;
380	}
381
382	len = PySequence_Fast_GET_SIZE(seq);
383	for (i = 0; i < len; i++) {
384		SEL sel;
385		PyObject* m;
386
387		cur = PySequence_Fast_GET_ITEM(seq, i);
388		if (cur == NULL) {
389			continue;
390		}
391
392		if (!PyObjCSelector_Check(cur)) {
393			continue;
394		}
395
396		sel = PyObjCSelector_GetSelector(cur);
397
398		m = findSelInDict(clsdict, sel);
399		if (m == NULL) {
400			m = PyObjCClass_FindSelector(super_class, sel, PyObjCSelector_IsClassMethod(cur));
401		}
402
403		if (m == NULL || !PyObjCSelector_Check(m)) {
404			Py_XDECREF(m);
405			if (PyObjCSelector_Required(cur)) {
406				PyErr_Format(PyExc_TypeError,
407					"class %s does not fully implement "
408					"protocol %S: no implementation for %s",
409					name,
410					self->name,
411					sel_getName(sel));
412				Py_DECREF(seq);
413				return 0;
414			} else {
415				PyErr_Clear();
416			}
417		} else {
418			if (!signaturesEqual(PyObjCSelector_Signature(m),
419				PyObjCSelector_Signature(cur)) != 0) {
420
421				PyErr_Format(PyExc_TypeError,
422					"class %s does not correctly implement "
423					"protocol %S: "
424					"the signature for method %s is "
425					"%s instead of %s",
426					name,
427					self->name,
428					sel_getName(sel),
429					PyObjCSelector_Signature(m),
430					PyObjCSelector_Signature(cur)
431				);
432				Py_DECREF(seq);
433				Py_DECREF(m);
434				return 0;
435			}
436			Py_DECREF(m);
437		}
438	}
439	Py_DECREF(seq);
440	return 1;
441}
442
443PyObject*
444PyObjCInformalProtocol_FindProtocol(SEL selector)
445{
446	PyObject* item;
447
448	if (selToProtocolMapping == NULL) return NULL;
449
450	item = PyDict_GetItemString(selToProtocolMapping, (char*)sel_getName(selector));
451	if (item != NULL) {
452		return item;
453	}
454
455	PyErr_Clear();
456	return NULL;
457}
458