أي مستخدم User يفضل أن يحفظ البرنامج الذي يستخدمه الإعدادات والألوان وحجم الخطوط وأي Customization مباشره بعد أن يقوم تعديل خصائص البرنامج ، وبالتالي بمجرد تشغيل التطبيق مره أخرى يبدأ المستخدم في العمل بدلا من اعاده تجهيز تلك الإعدادات مره أخرى ..
كمبرمج اذا أردت أن تطبيق مثل هذه الخاصية بدون أستخدام أي أدوات مساعدة سوف تضطر الى كتابة تلك الإعدادات في ملف في مكان ما على القرص ، ومن ثم تقوم بقرائه هذه الإعدادات عند كل مره تشغيل للبرنامج وتطبيق هذه الإعدادات في البرنامج ، الطريقة واضحه ولكنها سوف تستهلك الزمن خصوصا عند الكتابة والقرائه حيث يجب أن تكون وفق نسق معين تعرفه حتى تستطيع القرائه Parsing بشكل صحيح .
في جافا وفرت صن عدة API لتسهيل المبرمجين على حفظ إعدادات البرنامج ابتدائا من الكلاس Properties والذي وجد في أوائل نسخ الجافا ، ولكن كما سنرى بعد قليل أن العمل مع هذا الكلاس غير مضمون تماما في جميع الحالات وفقط يفضل استخدام هذا الكلاس عند اراده حفظ معلومات بسيطة .
بعد ذلك قامت صن في جافا 1.4 بتقديم الكلاس Preference الذي يحل كل المشاكل تماما واستخدامه أسهل بكثير أيضا من الProperties .
في هذه المقالة سوف نتناول هذه الAPI الخاصه بحفظ اعدادات البرامج (سواء الProperties و الPreferences ) ، وفي الأخير سوف نطرح مثال عملي متكامل حول هذا الموضوع ..
Property Maps
الProperty هو map لتخزين مجموعه من المفاتيح وما يقابلها من القيم Key/Value Pairs . وجافا تطبق هذا المفهوم في الكلاس Properties وفي هذا الكلاس يجب أن تكون المفاتيح والقيم كلها من النوع String والذي يميزه عن استخدام الملفات العادية هو سهولة التخزين واسترجاع البيانات بسهوله ، اضافة الى أنك يمكن أن تقدم قيم افتراضية في حال لم تكن هناك قيم في الملف .
مثال :
Properties settings = new Properties(); settings.put("title","This is simple example"); settings.put("color","Color.BLACK");
وعند إراده تخزين هذه البيانات سوف نستخدم الدالة store والتي تستقبل FileStream حتى تكتب فيه البيانات والمعامل الأخر هو نص يُكتب في أول سطر في الملف ، كما المثال :
FileOutputStream out = new FileOutputStream("mySettings.properties"); settings.store(out , "This is My Settings");
ومن المُفضل تخزين هذا الملف في مجلد المستخدم user home ، ويتم الإستعلام عن هذا المسار من خلال الدالة getProperty في الكلاس System :
String userDir = System.getProperty("user.home");
وعند إراده القرائه من الملف ، سوف نستخدم الدالة load ويمرر لها FileStream يتم القرائه منه ، كما يلي :
FileInputStream in = new FileInputStream("mySettings.properties"); settings.load(in);
لكن قد يحدث أن يتم التغيير في هذا الملف أو حتى حذفه ، لذلك يمكن أن نضع قيمه افتراضيه بحيث اذا لم يجد القيم في الملف يستخدم الإفتراضيه ، ويمكن ان نضع هذه القيم الإفتراضيه بطريقتين :
الأولى وهي من خلال استدعاء الدالة getProprty (في الكلاس Properties وليس System ) وتمرير المفتاح الذي نريد قيمته ويكون المعامل الأخر هو القيمه الإفتراضيه اذا لم يجد ذلك المفتاح :
String title = settings.getProperty("title","new title");
الطريقة الثانيه وهي أسهل وأسرع خاصه اذا كانت هناك كثير من الإعدادات تحتاج لقرائتها ، وفيها تقوم بعمل Properties أخر يحتوي على القيم الإفتراضيه وتقوم بتمريرها لدالة بناء الProperties الذي يحتوي على الإعدادت ، وحين يتم قرائه القيمه الأولى سيتجاهل الإفتراضيه بالطبع ، وهكذا لكل المفاتيح ، كما يلي المثال :
Properties defaultSettings = new Properties(); defaultSettings.put("title",new title"); defaultSettings.put("color","Color.BLACK); //.... Properties settings = new Properties( defaultSettings );
المثال التالي لواجهه Swing تحفظ حجم الJFrame والإسم بعد أغلاق البرنامج ، ويعود الحجم عند فتحك للتطبيق مره أخرى :
// use Java Properties File to store Application size and Title import java.awt.EventQueue; import java.awt.event.WindowListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.JOptionPane; import java.util.Properties; import java.io.File; import java.io.IOException; import java.io.FileInputStream; import java.io.FileOutputStream; public class PropTest { public static void main (String[] args ) { EventQueue.invokeLater ( new Runnable() { public void run () { PropFrame frame = new PropFrame(); frame.setVisible(true); } }); } } class PropFrame extends JFrame { public PropFrame () { // get size,position,title from properties file String userDir = System.getProperty("user.home"); File propertiesDir = new File ( userDir, ".myApp" ); if ( ! propertiesDir.exists() ) propertiesDir.mkdir(); propertiesFile = new File( propertiesDir , "here.test"); Properties defaultSettings = new Properties(); defaultSettings.put("left","0"); defaultSettings.put("top","0"); defaultSettings.put("width", "" + DEFAULT_WIDTH ); defaultSettings.put("height","" + DEFAULT_HEIGHT ); defaultSettings.put("title", "" ); settings = new Properties( defaultSettings ); if ( propertiesFile.exists() ) { try { FileInputStream in = new FileInputStream ( propertiesFile ); settings.load( in ); } catch (IOException ex ) { ex.printStackTrace(); } } // set size, position setBounds ( Integer.parseInt( settings.getProperty("left")) , Integer.parseInt( settings.getProperty("top")) , Integer.parseInt( settings.getProperty("width")), Integer.parseInt( settings.getProperty("height")) ); // if no title , get title from user String title = settings.getProperty("title"); if ( title.equals("") ) title = JOptionPane.showInputDialog("please supply frame title"); if ( title == null ) title = ""; setTitle(title ); addWindowListener ( new WindowAdapter() { public void windowClosing ( WindowEvent event ) { settings.put("left", "" + getX() ); settings.put("top", "" + getY() ); settings.put("width", "" + getWidth()); settings.put("height","" + getHeight()); settings.put("title",getTitle()); try { FileOutputStream out = new FileOutputStream( propertiesFile ); settings.store( out , "Program Settings"); } catch ( IOException ex ) { ex.printStackTrace(); } System.exit(0); } }); } private Properties settings; private File propertiesFile; private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT= 200; }
The Preferences API
بالرغم من الكلاس Properties أدى المهمه على أكمل وجه ، الا أنه قد تحدث مشاكل في حال وجد ملف بنفس إسم الملف الذي ستحفظ فيه الإعدادات ، اضافة الى انه يدخلك في تفاصيل التخزين الى ملف والقرائه منه وكثير من المبرمجين الكسالى يريدون تجريد أكثر من ذلك وأنا أولهم ..
الكلاس Preferences أتي لحل هذه المشاكل ، فهو يقوم بحفظ الإعدادات في مكان ما على حسب البيئة التي يعمل عليها البرنامج ، فاذا عمل البرنامج تحت نظام ويندوز فسوف يخزن تلك الإعدادات في مسجل النظام Registry ، أما اذا عمل تحت لينوكس سوف يخزنها في مكان في نظام الملفات . ويمكن حفظ هذه الإعدادت للمستخدم الحالى User ، أما لأي مستخدم في النظام System . كما يبين المثال :
Preferences root = Preferences.userRoot(); // for user Preferences root = Preferences.systemRoot(); // for system
بعد ذلك يمكنك وضع الإعدادت في المكان الذي تريد ويفضل أن تكون كما في تقسيم الباكج ، المثال التالي يضعها في com/mycompany/myapp
Preferences node = root.node("/com/mycompany/myapp");
اذا أردت أن تضع المسار كما في باكج الكلاس الحالي يمكنك أن تستخدم (فقط غير obj بthis ) :
Preferences node = Preferences.userNodeForPackage(obj.getClass()); Preferences node = Preferences.systemNodeForPackage(obj.getClass());
لقرائه الإعدادت ، سوف تستخدم أحدى الدوال التالية ، وكلها تستقبل المفتاح والقيمه الإفتراضيه وترجع القيمه القيمه المناسبة (الإفتراضية اذا لم توجد قيمه للمفتاح والا القيمه الموجودة ) :
• String get(String key, String defval) • int getInt(String key, int defval) • long getLong(String key, long defval) • float getFloat(String key, float defval) • double getDouble(String key, double defval) • boolean getBoolean(String key, boolean defval) • byte[] getByteArray(String key, byte[] defval)
وللكتابة سوف تستخدم :
• void put(String key, String value) • void putInt(String key, int value) • void putLong(String key, long value) • void putFloat(String key, float value) • void putDouble(String key, double value) • void putBoolean(String key, boolean value) • void putByteArray(String key, byte[] value)
ويمكن اذا أردت أن تنقل البرنامج من بيئة لأخرى أن تقوم بعمل export للإعدادت في ملف xml ، وهناك قم بعمل import للإعدادات ، وذلك من خلال الدوال التالية:
void exportSubtree(OutputStream out) void exportNode(OutputStream out) void importPreferences(InputStream in)
Text-To-Speech Application :
قمت بعمل مثال بسيط ، لبرنامج لقرائه النصوص باستخدام المكتبة TTS ، أيضا استخدمت واجهات للبرنامج تسمى Substance ، وفي حال قام المستخدم بتغيير الواجهه أو تغيير صوت القارئ ومن ثم خرج من البرنامج فسوف تعود الإعدادات مره أخرى بعد فتح التطبيق .. أيضا اذا تمت الكتابة في JTextArea وتم الخروج من البرنامج فسوف ترجع ما كان موجود مرة أخرى عند فتح التطبيق ..
لترجمه وتشغيل التطبيق ، حمل المكتبة Substance وضع الملف substance.jar في مسار البرنامج ، أيضا حمل المكتبة TTS وضع مجلد lib بالكامل في مسار البرنامج ، وأفضل أن تقوم بعمل ملف الباتش make.bat هذا للتسهيل وقم بتشغيله :
cls @echo off javac -cp .;lib\freetts.jar TextToSpeech.java if not errorlevel 1 java -cp .;lib\freetts.jar;substance.jar TextToSpeech pause
أما من يريد الكود + المكتبة للتجربة فقط فيمكنه تحميل هذا الملف ( 10 ميغا ):
Code + Library
وقم بتشغيل ملف الmake.bat ..