مقدمة
يتم كتابة جميع الكود في هذه المقالة في JavaScript ، ولكن يمكنك أيضًا استخدام لغات البرمجة النصية المتوافقة مع JSR 223 الأخرى. يمكن تشغيل هذه الأمثلة كملفات نصية أو في قذائف تفاعلية عن طريق تشغيل بيان واحد في وقت واحد. بناء الجملة للوصول إلى خصائص وطرق الكائنات في JavaScript هي نفسها لغة Java.
تحتوي هذه المقالة على الأجزاء التالية:
1. الوصول إلى فصول جافا
من أجل الوصول إلى الأنواع الأصلية أو أنواع Java المرجعية في JavaScript ، يمكنك الاتصال بوظيفة Java.type() ، والتي تُرجع نوع الكائن المقابل بناءً على اسم الفئة الكاملة. يوضح الرمز التالي كيفية الحصول على أنواع كائنات مختلفة:
var arraylist = java.type ("java.util.arraylist") ؛ var inttype = java.type ("int") ؛ var stringArrayType = java.type ("java.lang.string []") ؛ var int2darraype = java.type ("]] تشبه طريقة إرجاع كائن النوع باستخدام وظيفة Java.type() في JavaScript تلك الموجودة في Java.
على سبيل المثال ، يمكنك إنشاء فئة باستخدام الطريقة التالية:
var anarraylist = new java.type ("java.util.arraylist") ؛يمكن استخدام كائنات نوع Java لتشكيل كائنات Java. يوضح الرمز التالي كيفية إنشاء مثيل لكائن جديد باستخدام المُنشئ الافتراضي واستدعاء المُنشئ الذي يحتوي على المعلمات:
var arraylist = java.type ("java.util.arraylist") ؛ var defaultizizearraylist = new ArrayList ؛ var customsizearraylist = new ArrayList (16) ؛ يمكنك استخدام طريقة Java.type() للحصول على نوع الكائن ، ويمكنك استخدام الأساليب التالية للوصول إلى الخصائص والأساليب الثابتة:
var file = java.type ("java.io.file") ؛ file.createTempFile ("nashorn" ، ".tmp") ؛ إذا كنت ترغب في الوصول إلى الفئة الثابتة الداخلية ، فيمكنك تمرير علامة الدولار $ إلى طريقة Java.type() .
يوضح الرمز التالي كيفية إرجاع الفئة الداخلية Float من java.awt.geom.Arc2D :
var float = java.type ("java.awt.geom.arc2d $ float") ؛إذا كان لديك بالفعل كائن نوع فئة خارجي ، فيمكنك الوصول إلى فئة داخلية تمامًا مثلما يمكنك الوصول إلى الخاصية ، كما هو موضح أدناه:
var arc2d = java.type ("java.awt.geom.arc2d") var float = arc2d.floatنظرًا لأنها فئة داخلية غير استاتيكية ، يجب تمرير مثيل الفئة الخارجية كمعلمة إلى المنشئ.
على الرغم من أن استخدام كائنات النوع في JavaScript يشبه ذلك في Java ، إلا أنه لا يزال مختلفًا إلى حد ما عن كائن java.lang.Class . هذا الاختلاف هو قيمة إرجاع طريقة getClass() . يمكنك استخدام خصائص class static للحصول على هذه المعلومات.
يوضح الرمز التالي الفرق بين الاثنين:
var arraylist = java.type ("java.util.arraylist") ؛ var a = new ArrayList ؛ // جميع ما يلي صحيح: print ("النوع بمثابة هدف لمثلة:" + (a extremof arraylist)) ؛ print ("الفئة لا تهدف إلى مثيل:" +! (A.GetClass ()! == ArrayList)) ؛ print ("خاصية النوع` class` هي نفس مثيل getClass (): " + (A.GetClass () === ArrayList.class)) ؛ print (" النوع هو نفس خاصية `static` من getClass () + (A.GetClass ().بناء الجملة والدلالات ، تعبيرات فئة JavaScript وكائنات وقت التشغيل تشبه دلالات Java. ومع ذلك ، في Java ، لا يحتوي كائن الفئة على خاصية تسمى ثابتًا لأن تعبير الفئة المترجم لا يتم استخدامه ككائن.
2. استيراد حزم والفصول java
من أجل الوصول إلى فئات Java استنادًا إلى اسمها البسيط ، يمكننا استخدام وظائف importPackage() و importClass() لاستيراد حزم ودروس Java. توجد هذه الوظائف في ملف البرنامج النصي (Mozilla_compat.js).
يوضح المثال التالي كيفية استخدام وظائف importPackage() و importClass() :
// load compatibility scriptload ("nashorn: mozilla_compat.js") ؛ // استيراد java.awt packageimportpackage (java.awt) ؛ // import java.awt.frame classimportclass (java.awt.frame) ؛ setVisible () methodframe.setVisible (true) ؛ // الوصول إلى javabean propertyprint (frame.title) ؛ يمكن الوصول إلى حزم Java من خلال المتغير العالمي للحزم ، مثل Packages.java.util.Vector أو Packages.javax.swing.JFrame . ومع ذلك ، فإن حزمة Java SE القياسية لها طرق وصول أبسط ، مثل: Java تتوافق مع Packages.java ، Javax يتوافق مع packages.javax ، و Org يتوافق مع packages.org.
لا تتطلب حزمة Java.lang الاستيراد افتراضيًا ، لأن هذا سيتعارض مع كائنات JavaScript الأخرى المدمجة مثل Object Boolean Math . بالإضافة إلى ذلك ، قد يتسبب استيراد أي حزم وفئات Java أيضًا في تعارضات في الأسماء المتغيرة تحت النطاق العالمي لـ JavaScript. لتجنب النزاعات ، نحدد كائن Javaimporter والحد من نطاق حزم وفئات Java المستوردة من خلال البيان with ، كما هو موضح في الكود التالي:
// إنشاء كائن javaimporter مع حزم وفئات محددة لاستيراد GUI = New Javaimporter (Java.awt ، Javax.swing) ؛ // تمرير كائن Javaimporter إلى "مع" مع "AWT". var jframe = new JFrame ("swing jframe") ؛} ؛3. استخدم صفائف Java
من أجل إنشاء كائن صفيف Java ، تحتاج أولاً إلى الحصول على كائن نوع صفيف Java وتهيئته. إن سمة بناء الجملة length للوصول إلى JavaScript إلى عناصر الصفيف هي نفسها Java ، كما هو موضح في الكود التالي:
var stringArray = java.type ("java.lang.string []") ؛ var a = new StringArray (5) ؛ // قم بتعيين قيمة العنصر الأول [0] = "البرمجة النصية رائعة!" ؛ بالنظر إلى صفيف JavaScript ، يمكننا أيضًا تحويله إلى صفيف Java باستخدام طريقة Java.to() . نحتاج إلى تمرير صفيف JavaScript كمعلمة إلى الطريقة وتحديد نوع الصفيف المراد إرجاعه ، والذي يمكن أن يكون سلسلة أو كائن نوع. يمكننا أيضًا تجاهل معلمات كائن النوع لإرجاع مجموعة الكائن []. يتم تنفيذ عملية التحويل وفقًا لقواعد تحويل ECMASCRIPT. يوضح الرمز التالي كيفية تحويل مجموعة JavaScript إلى صفيف Java من خلال معلمات Java.to() مختلفة:
// إنشاء صفيف javaScript var anarray = [1 ، "13" ، خطأ] ؛ // تحويل صفيف إلى java int [] array var javaintarray = java.to (Anarray ، "int []") ؛ print (javaintarray [0]) ؛ // يطبع الرقم 1print (Javaintarray [1]) ؛ // يطبع الرقم 13print (Javaintarray [2]) ؛ // يطبع الرقم 0 // تحويل صفيف JavaScript إلى JavastringArray = java.to (Anarray ، Java.type ("java.lang.string [])) ؛ print (javastringarray [0]) ؛ // تطبع السلسلة "1" طباعة (JavastringArray [1]) ؛ // يطبع السلسلة "13" طباعة (JavastringArray [2]) ؛ // يطبع السلسلة "false" // تحويل صفيف javaScript إلى كائن java [] array var javaobjectarray = java.to (anarray) ؛ print (javaobjectarray [0]) ؛ // يطبع الرقم 1print (JavaObjectArray [1]) ؛ // يطبع السلسلة "13" طباعة (JavaObjectArray [2]) ؛ // يطبع القيمة المنطقية "خطأ" يمكنك استخدام طريقة Java.from() لتحويل صفيف Java إلى مجموعة JavaScript.
يوضح الرمز التالي كيفية تحويل صفيف يحتوي على قائمة الملفات في الدليل الحالي إلى صفيف JavaScript:
// الحصول على ملف نوع ملف java objectvar = java.type ("java.io.file") ؛ // إنشاء صفيف java من ملفات الملف listcurdir = new file (".) ArrayPrint (JSlist) ؛يلاحظ:
في معظم الحالات ، يمكنك استخدام كائنات Java في البرنامج النصي الخاص بك دون تحويلها إلى كائنات JavaScript.
4. تنفيذ واجهة Java
يشبه بناء جملة تنفيذ واجهات Java في JavaScript طريقة تحديد الفئات المجهولة في Java. نحتاج فقط إلى إنشاء إنشاء الواجهة وتنفيذ أساليبها مع وظائف JavaScript.
يوضح الرمز التالي كيفية تنفيذ واجهة Runnable :
// قم بإنشاء كائن ينفذ الواجهة القابلة للتشغيل عن طريق تطبيق // طريقة Run () كدالة JavaScript R = new java.lang.runnable () {run: function () {print ("running .../n") ؛ }} ؛ // يمكن نقل المتغير R إلى أساليب Java التي تتوقع كائنًا تنفيذًا // java.lang.runnable interfacevar th = new java.lang.thread (r) ؛ th.start () ؛ th.join () ؛ إذا كانت الطريقة تريد ما كائنًا ، فإن هذا الكائن يقوم بتنفيذ واجهة بطريقة واحدة فقط ، فيمكنك تمرير وظيفة البرنامج النصي إلى هذه الطريقة بدلاً من تمرير الكائن. على سبيل المثال ، في المثال أعلاه ، يتطلب مُنشئ Thread() كائنًا ينفذ الواجهة Runnable كمعلمة. يمكننا الاستفادة من التحويل التلقائي لتمرير وظيفة البرنامج النصي إلى مُنشئ Thread() .
يوضح المثال التالي كيفية إنشاء كائن Thread دون تطبيق واجهة Runnable :
// تحديد وظيفة JavaScript FunctionFunction Func () {print ("i am func!") ؛} ؛ // تمرير وظيفة javaScript بدلاً من كائن ينفذ // java.lang.runnable interfacevar th = new java.lang.thread (func) ؛ th.start () ؛ يمكنك تنفيذ واجهات متعددة عن طريق تمرير كائنات النوع ذات الصلة إلى وظيفة Java.extend() .
5. تمديد فصول جافا الملخص
يمكنك إنشاء إنشاء فئة فرعية من فئة مجردة مجهولة ، فقط تمرير كائن JavaScript إلى المُنشئ ، والذي يحتوي على بعض الخصائص المقابلة للقيم التي تنفذها طريقة الفئة التجريدية. إذا تم زيادة تحميل الطريقة ، فستوفر وظيفة JavaScript تطبيقات لجميع متغيرات الطريقة. يوضح المثال التالي كيفية تهيئة الفئة الفرعية من timertask الفئة التجريدية:
var timertask = java.type ("java.util.timertask") ؛ var task = new timertask ({run: function () {print ("Hello World!")}}) ؛ بالإضافة إلى استدعاء المُنشئ والمعلمات المارة ، يمكننا أيضًا تقديم معلمات مباشرة بعد التعبير new .
يوضح المثال التالي كيفية استخدام هذا الجملة (على غرار تعريف الفئات الداخلية المجهولة في Java) ، وهو أبسط قليلاً من المثال أعلاه:
var task = new timertask {run: function () {print ("Hello World!")}} ؛إذا كانت الفئة التجريدية تحتوي على طريقة مجردة واحدة (نوع SAM) ، فلن نحتاج إلى تمرير كائن JavaScript إلى المُنشئ ، فيمكننا تمرير واجهة وظيفة تنفذ الطريقة. يوضح المثال التالي كيفية استخدام أنواع SAM لتبسيط الرمز:
var task = new timertask (function () {print ("Hello World!")}) ؛بغض النظر عن بناء الجملة الذي تختاره ، إذا كنت بحاجة إلى استدعاء مُنشئ يحتوي على معلمات ، يمكنك تحديد المعلمات في كائن التنفيذ والوظيفة.
إذا كنت ترغب في استدعاء طريقة Java التي تتطلب معلمات نوع SAM ، فيمكنك تمرير وظيفة JavaScript إلى الطريقة. ستقوم Nashorn بتشكيل فئة فرعية وفقًا لاحتياجات الطريقة واستخدام هذه الوظيفة لتنفيذ طريقة التجريد الفريدة.
يوضح الرمز التالي كيفية استدعاء طريقة Timer.schedule() ، والتي تتطلب كائن timertask كمعلمة:
var timer = java.type ("java.util.timer") ؛ timer.schedule (function () {print ("Hello World!")}) ؛يلاحظ:
يفترض بناء الجملة السابق أن نوع SAM المطلوب هو واجهة أو يحتوي على مُنشئ افتراضي يستخدمه Nashorn لتهيئة الفئة الفرعية. هذا لا يمكن استخدام فئة لا تحتوي على مُنشئ افتراضي.
6. تمديد فصول جافا محددة
لتجنب الالتباس ، لا يمكن استخدام بناء الجملة لتوسيع الطبقات التجريدية لتوسيع فصول الخرسانة. لأنه يمكن إنشاء إنشاء فئة ملموسة ، يتم تحليل مثل هذا الجملة في محاولة لإنشاء مثيل فئة جديد وتمرير كائن الفئة المطلوبة من قبل المُنشئ (إذا كان نوع الكائن المتوقع واجهة). لإظهار هذه المشكلة ، يرجى إلقاء نظرة على رمز العينة التالي:
var t = new java.lang.thread ({run: function () {print ("thread running!")}}) ؛ يتم تحليل سطر الكود هذا لتوسيع فئة Thread وتنفيذ طريقة run() ، ويتم تمرير إنشاء فئة مؤشر Thread إلى مُنشئها كائن يقوم بتطبيق الواجهة القابلة للتشغيل.
لتمديد فئة ملموسة ، تمرير كائن النوع الخاص به إلى وظيفة Java.extend() ، ثم إرجاع كائن النوع الخاص به إلى فئته الفرعية. ثم يمكنك استخدام كائن النوع لهذا الفئة الفرعية لإنشاء مثيلات وتوفير تطبيقات طريقة إضافية.
سوف يوضح لك الرمز التالي كيفية تمديد فئة Thread وتنفيذ طريقة run() :
var thread = java.type ("java.lang.thread") ؛ var threadextender = java.extend (thread) ؛ var t = new threadextender () {run: function () {print ("thread running!)}} ؛ يمكن أن تحصل وظيفة Java.extend() على قائمة بأنواع متعددة من الكائنات. يمكنك تحديد أكثر من كائن نوع Java ، أو يمكنك تحديد عدد كائنات النوع مثل واجهات Java. يمتد كائن النوع الذي تم إرجاعه الفئة المحددة (أو java.lang.Object ، إذا لم يكن هناك كائن نوع محدد) ، فإن هذه الفئة تنفذ جميع الواجهات. لا تحتاج كائنات النوع للفئة إلى أن تكون في الجزء العلوي من القائمة.
7. طرق للوصول إلى الفئة الفائقة (فئة الوالدين)
يمكن للطرق التي ترغب في الوصول إلى فئة الأصل استخدام وظيفة Java .super() .
يوضح المثال التالي كيفية تمديد فئة java.lang.Exception والوصول إلى طرق الفئة الأصل.
مثال 3-1 طريقة للوصول إلى الفئة الأصل (super.js) var issection = java.type ("java.lang.exception") ؛ var issectionAdapter = java.extend (استثناء) ؛ var استثناء = استثناء جديد ("استثناء) {getMessage: function () {var _super_ = Java.super (" استثناء) ؛ return _super_.getMessage (). ToupperCase () ؛ }} جرب {استثناء رمي ؛} catch (ex) {print (استثناء) ؛}إذا قمت بتشغيل الرمز أعلاه ، فسوف تقوم بطباعة ما يلي:
jdk.nashorn.javaadapters.java.lang.Exception: رسالة الاستثناء الخاصة بي
8. ملزمة للتنفيذ إلى الفصل
في القسم السابق ، وصفنا كيفية تمديد فئات Java وتنفيذ الواجهة باستخدام معلمة كائن JavaScript إضافية. يرتبط التنفيذ بمثيل محدد ، يتم إنشاؤه من خلال الفصل الجديد ، وليس الفئة بأكملها. هناك بعض الفوائد للقيام بذلك ، مثل بصمة الذاكرة في وقت التشغيل ، حيث يمكن لـ Nashorn إنشاء محول عالمي واحد لمجموعة من أنواع كل تطبيق.
يوضح المثال التالي أن الحالات المختلفة يمكن أن تكون هي نفس فئة Java ، لكن كائنات تنفيذ JavaScript مختلفة:
var runnable = java.lang.runnable ؛ var r1 = new Runnable (function () {print ("أنا Runnable 1!")}) ؛ var r2 = new Runnable (function () {print ("أنا متشارك 2!")}) ؛ r1.run () ؛ r2.run ()سيقوم الرمز أعلاه بطباعة النتيجة التالية:
أنا قابلة للتشغيل 1! أنا قابلة للتشغيل 2! نحن نشارك نفس الفصل: صحيح
إذا كنت ترغب في تمرير مثيل لفئة ما إلى واجهة برمجة تطبيقات خارجي (مثل إطار Javafx ، أو تمرير مثيل التطبيق إلى Javafx API) ، فيجب عليك تمديد فئة Java أو تنفيذ واجهة مرتبطة بتلك الفئة ، بدلاً من مثيلها. يمكنك تنفيذ الفئة عن طريق تمرير كائن JavaScript ملزم ونقله إلى المعلمة الأخيرة من وظيفة Java.Extend (). هذا ينشئ فئة جديدة مع نفس مُنشئ الفئة الأصلية ، لأنها لا تتطلب تنفيذًا إضافيًا لمعلمات الكائن.
يوضح المثال التالي كيفية ربط التنفيذ في فئة ويوضح أن فئات التنفيذ مختلفة عن المكالمات المختلفة في هذه الحالة:
var runnableImpl1 = java.extend (java.lang.runnable ، function () {print ("أنا قابلة للتشغيل 1!")}) ؛ var runnableImpl2 = java.extend (java.lang.runnable ، function () {print ("أنا runnable 2!")}) runNableImpl2 () ؛ r1.run () ؛ r2.run () ؛ print ("نحن نشارك نفس الفئة:" + (r1.class === r2.class)) ؛نتائج التنفيذ مثال أعلاه هي كما يلي:
أنا قابلة للتشغيل 1! أنا Runnable 2! نحن نشارك نفس الفصل: خطأ
إن نقل كائن التنفيذ من استدعاء مُنشئ إلى استدعاء دالة Java.extend() يتجنب المعلمات الإضافية المطلوبة في مكالمة المنشئ. تتطلب كل مكالمة إلى وظيفة Java.extend() كائن تنفيذ للفئة المحددة لإنشاء فئة محول Java جديدة. لا يزال بإمكان فئات المحول التي يتم تنفيذها مع حدود الفصل استخدام معلمة مُنشئ إضافية لزيادة تجاوز سلوك مثيل معين. حتى تتمكن من دمج هاتين الطريقتين: يمكنك توفير جزء من تطبيق JavaScript في فئة أساسية ، ثم تمريره إلى وظيفة Java.extend() ، وتوفير تطبيق مثيل في الكائن ونقله إلى المنشئ. سيتم كتابة بعض تعريفات وظائف الكائن عند تحديد الكائن وتمريره إلى المنشئ.
يوضح الرمز التالي كيفية الكتابة فوق دالة كائن حدود الفئة عن طريق تمرير وظيفة إلى المُنشئ:
var runNableImpl = java.extend (java.lang.runnable ، function () {print ("أنا قابلة للتشغيل 1!")}) ؛ var r1 = new RunNableImpl () ؛ var r2 = new raNeableImpl (function () {print ("أنا قابلة للتشغيل 2!") ؛ r1.run () ؛ (r1.class === r2.class)) ؛نتائج الطباعة بعد تنفيذ المثال أعلاه هي كما يلي:
أنا قابلة للتشغيل 1! أنا قابلة للتشغيل 2! نحن نشارك نفس الفصل: صحيح
9. حدد طريقة التحميل الزائد للأسلوب
يمكن زيادة تحميل طرق Java باستخدام أنواع المعلمات المختلفة. سيقوم برنامج التحويل البرمجي Java (Javac) بتحديد الطريقة الصحيحة للتنفيذ في وقت الترجم. يتم تنفيذ تحليل طرق Java الزائدة في ناشورن عندما يتم استدعاء الطريقة. إنها أيضًا طريقة لتحديد الطريقة الصحيحة بناءً على نوع المعلمة. ولكن إذا كان نوع المعلمة الفعلي سيؤدي إلى غموض ، فيمكننا تحديد متغير محدد محدد. هذا يحسن أداء تنفيذ البرنامج ، لأن محرك ناشورن لا يحتاج إلى التمييز بين الطريقة التي يمكن الاتصال بها أثناء المكالمة.
تتعرض المتغيرات المحملة على أنها خصائص خاصة. يمكننا الرجوع إليهم في شكل سلاسل ، والتي تحتوي على أسماء الطرق وأنواع المعلمات ، وتحيط بها أقواس.
يوضح المثال التالي كيفية استدعاء طريقة System.out.println() مع متغير معلمة Object ، نقوم بتمرير سلسلة "Hello" إليها:
var out = java.lang.system.out ؛ out ["" println (object) "] (" Hello ") ؛في المثال أعلاه ، فإن استخدام اسم فئة الكائن وحده يكفي لأنه هو التوقيع الذي يحدد بشكل فريد التصنيف الصحيح. الحالة التي يتعين عليك فيها استخدام اسم الفئة الكاملة هي أن وظيفتين متغيرتين تم تحميلهما باستخدام أنواع مختلفة من المعلمات ، لكن النوع له نفس الاسم (هذا ممكن ، على سبيل المثال ، تحتوي الحزم المختلفة على نفس اسم الفئة).
10. تعيين أنواع البيانات
الغالبية العظمى من التحويلات السابقة Java و JavaScript تعمل بشكل جيد كما تتوقع. في الفصول السابقة ، ذكرنا بعض تعيينات نوع البيانات البسيطة بين Java و JavaScript. على سبيل المثال ، يمكن تحويل بيانات نوع الصفيف بشكل صريح ، يمكن تحويل وظائف JavaScript تلقائيًا إلى أنواع SAM عند تمريرها كمعلمات إلى طرق Java. يقوم كل كائن JavaScript بتنفيذ واجهة java.util.Map للسماح لـ API بقبول التعيينات مباشرة. عند نقل القيم إلى Java API ، سيتم تحويلها إلى النوع العددي المستهدف المتوقع ، والذي يمكن أن يكون نوعًا من التغليف أو نوع بيانات بدائية. إذا لم يكن النوع المستهدف مؤكدًا للغاية (مثل الرقم) ، فيمكنك فقط أن تطلب منه أن يكون نوع الرقم ، ومن ثم تغليف النوع على وجه التحديد ، مثل المزدوج ، عدد صحيح ، طويل ، إلخ. يجعل التحسين الداخلي القيمة العددية لأي نوع حزمة. الزملاء ، يمكنك تمرير أي قيمة JavaScript إلى Java API ، سواء كان نوعًا مغلفًا أو نوعًا بدائيًا ، لأن خوارزمية تحويل ToNumber JavaScript ستعالج قيمتها تلقائيًا. إذا كانت طريقة Java تتطلب معلمة String أو كائن Boolean ، فسيستخدم JavaScript تحويلات ToString و ToBoolean للحصول على قيمتها.
يلاحظ:
نظرًا لاعتبارات تحسين الأداء الداخلي لعمليات السلسلة ، لا تتوافق سلاسل JavaScript دائمًا مع نوع java.lang.string ، أو قد تكون أيضًا نوع java.lang.CharSequence . إذا قمت بتمرير سلسلة javaScript إلى طريقة Java تتطلب معلمة java.lang.String ، فإن سلسلة JavaScript هي نوع java.lang.String ، ولكن إذا كان توقيع طريقة الخاص بك يريد أن يكون أكثر عاما (على سبيل المثال ، فإن نوع المعلمة المقبولة هو CharSequence الأمر لا ينفص.
لخص
ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون بعض المساعدة لدراسة الجميع والعمل. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل.