TC65 : Settings management

Someone recently asked me and the javacint group how we should handle settings. So I’ll give you two answers :

  • ejw’s reply : You should use some simple DataInputStream and DataOutputStream objects.
  • Mine : If you only store simple values (numbers and text), you should use a simple text file. It enables you to easily see and modify settings outside the TC65.

So, my little gift of today will be a simple settings management class. The idea is that this file is in a very simple format (that can be read in any PC) and it only stores settings that have changed. This is very important, it allows you to change the default behavior at next software update but also enable you to override some of the settings for each chip (like the last IMSI identifier of the SIM card).

I did something wrong in this example by writing all the methods with a starting Uppercase letter. The reason is that I do like a it a lot better like that (this is the C# style) but style, the good thing to do is to stick to each language’s coding conventions. I can’t really update this code as I have modified this class to a more complex version where other class register to this one to receive settings change event.

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package Common;
 
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import com.siemens.icm.io.file.*;
 
/**
 * Settings management class
 * @author Florent Clairambault
 */
public class Settings {
 
        // The cached settings
	private Hashtable _settings;
 
        // File of the settings
	private final String _fileName = "a:/settings.txt";
 
        // Singleton instance of the settings
        private static Settings _instance;
 
	/**
	 * Get Default instance of the Settings class
	 * @return The default instance of the Settings class
	 */
	public static synchronized Settings getInstance() {
		if ( _instance == null )
			_instance = new Settings();
		return _instance;
	}
 
	/**
	 * Free the singleton instance
	 */
	public static synchronized void freeInstance() {
		_instance = null;
	}
 
	/**
	 * Load settings
	 */
	public synchronized void load() {
		StringBuffer buffer = new StringBuffer();
		Hashtable settings = getDefaultSettings();
		try {
 
 
			FileConnection fc = (FileConnection) Connector.open( "file:///" + _fileName, Connector.READ );
			InputStream is = fc.openInputStream();
 
			while ( is.available() > 0 ) {
				int c = is.read();
 
				if ( c == '\n' ) {
					loadTreatLine( settings, buffer.toString() );
					buffer = new StringBuffer();
				} else
					buffer.append( (char) c );
			}
			is.close();
			fc.close();
 
		} catch ( IOException ex ) {
			// The exception we shoud have is at first launch : 
			// There shouldn't be any file to read from
 
			if ( Logger.E_CRITICAL )
				Logger.Log( 19, "Settings.Load", ex );
		}
		_settings = settings;
	}
 
	/**
	 * Treat each line of the file
	 * @param def Default settings
	 * @param line Line to parse
	 */
	private static void loadTreatLine( Hashtable settings, String line ) {
		if ( Logger.E_VERBOSE )
			Logger.Log( 78, "loadTreatLine( [...], \"" + line + "\" );" );
		String[] spl = Common.strSplit( '=', line );
		String key = spl[0];
		String value = spl[1];
 
		// If default settings hashTable contains this key
		// we can use this value
		if ( settings.containsKey( key ) ) {
			settings.remove( key );
			settings.put( key, value );
		}
 
	}
 
 
	/**
	 * Get default settings
	 * @return Default settings Hashtable
	 */
	private Hashtable getDefaultSettings() {
		Hashtable defaultSettings = new Hashtable();
 
		// General M2MSoft settings :
		defaultSettings.put( "code", "8888" );
		defaultSettings.put( "servers", "87.106.206.30:3000" );
		defaultSettings.put( "apn", "gprs,m2minternet,\"\",\"\",,0" );
		defaultSettings.put( "imsi", "0000" );
		defaultSettings.put( "watchdogtimer", "20" );
		defaultSettings.put( "version", "0" );
		defaultSettings.put( "phoneManager", "+33686955405" );
 
		return defaultSettings;
	}
 
	/**
	 * Reset everything
	 */
	public synchronized void resetEverything() {
		try {
			FileConnection fc = (FileConnection) Connector.open( "file:///" + _fileName, Connector.READ_WRITE );
			if ( fc.exists() )
				fc.delete();
                        _settings = new Hashtable();
		} catch ( Exception ex ) {
			if ( Logger.E_CRITICAL )
				Logger.Log( 16725, "Settings.ResetEverything", ex );
		}
	}
 
	/** 
	 * save setttings
	 */
	public synchronized void save() {
 
		// If there's no settings, we shouldn't have to save anything
		if ( _settings == null )
			return;
 
		try {
			Hashtable defSettings = getDefaultSettings();
			Enumeration e = defSettings.keys();
			FileConnection fc = (FileConnection) Connector.open( "file:///" + _fileName, Connector.READ_WRITE );
			if ( fc.exists() )
				fc.delete();
			//fc = (FileConnection) Connector.open("file:///" + _fileName, Connector.READ_WRITE);
			fc.create();
			OutputStream os = fc.openOutputStream();
 
			while ( e.hasMoreElements() ) {
				String key = (String) e.nextElement();
				String value = (String) _settings.get( key );
				String defValue = (String) defSettings.get( key );
 
				if ( // if there is a default value
					defValue != null && // and
					// the value isn't the same as the default value
					defValue.compareTo( value ) != 0 ) {
					String line = key + "=" + value + '\n';
 
					if ( Logger.E_DEBUG )
						Logger.Log( 131, "Settings.save.line = \"" + line + "\"" );
 
					os.write( line.getBytes() );
				}
 
			}
			os.flush();
			os.close();
			fc.close();
		} catch ( Exception ex ) {
			if ( Logger.E_CRITICAL )
				Logger.Log( 131, "Settings.save", ex );
		}
 
	}
 
	/**
	 * Init (and ReInit) method
	 */
	private void checkLoad() {
		if ( _settings == null )
			load();
	}
 
	/**
	 * Get a setting's value as a String
	 * @param key Key Name of the setting
	 * @return String value of the setting
	 */
	public synchronized String getSetting( String key ) {
		checkLoad();
		if ( _settings.containsKey( key ) )
			return (String) _settings.get( key );
		else
			return null;
	}
 
	/**
	 * Sets a setting
	 * @param key Setting to set
	 * @param value Value of the setting
	 */
	public synchronized void setSetting( String key, String value ) {
		checkLoad();
		if ( _settings.containsKey( key ) )
			_settings.remove( key );
 
		_settings.put( key, value );
 
		OnSettingChanged( key );
	}
 
	/**
	 * Get a setting's value as an int
	 * @param key Key Name of setting
	 * @return Integer value of the setting
	 * @throws java.lang.NumberFormatException When the int cannot be parsed
	 */
	public int getSettingInt( String key ) throws NumberFormatException {
		String value = getSetting( key );
 
		if ( value == null )
			return -1;
 
		return Integer.parseInt( value );
	}
}

Latter on, I added some settings consumer capacities. It allows to use this class in some sort of “third party” developed components. Each component had to register itself to the settings management class, the settings management class could then request the default value settings it will provide, and it could set/get settings. The settings event class also launched an event when a setting was changed.

2 thoughts on “TC65 : Settings management”

  1. Seems valid. In tc65 I used a INI file reader/writer that used sections and values, and now I am using XML that is as it names says extendable.

    But why some variables you name starting with underscore and some methods you start with upper caption letter, and other with lower? Like in public int GetSettingInt( String key ) and private Hashtable getDefaultSettings() .. What is your pattern?

  2. I started doing this code in C# style coding and then I refactored it to what it should always have been: java style coding: http://static.webingenia.com/doc/com.webingenia.m2mp/com/webingenia/m2mp/settings/Settings.html

    The idea behind that is that I really prefer C# coding rules but in the end I decided it’s best to keep every language’s rule.

    For the variables, i use the “_” prefix for the private member variables and “_” suffix for the static variables.

    For the settings file, I try to keep a basic settings file and then a project specific settings format. It can be XML or anything else.

Leave a Reply

Your email address will not be published. Required fields are marked *