ResourceWriter.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. //
  2. // System.Resources.ResourceWriter.cs
  3. //
  4. // Authors:
  5. // Duncan Mak <[email protected]>
  6. // Dick Porter <[email protected]>
  7. //
  8. // (C) 2001, 2002 Ximian, Inc. http://www.ximian.com
  9. //
  10. //
  11. // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  12. //
  13. // Permission is hereby granted, free of charge, to any person obtaining
  14. // a copy of this software and associated documentation files (the
  15. // "Software"), to deal in the Software without restriction, including
  16. // without limitation the rights to use, copy, modify, merge, publish,
  17. // distribute, sublicense, and/or sell copies of the Software, and to
  18. // permit persons to whom the Software is furnished to do so, subject to
  19. // the following conditions:
  20. //
  21. // The above copyright notice and this permission notice shall be
  22. // included in all copies or substantial portions of the Software.
  23. //
  24. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  25. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  26. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  27. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  28. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  29. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  30. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  31. //
  32. using System.IO;
  33. using System.Collections;
  34. using System.Text;
  35. using System.Runtime.Serialization;
  36. using System.Runtime.Serialization.Formatters.Binary;
  37. namespace System.Resources
  38. {
  39. public sealed class ResourceWriter : IResourceWriter, IDisposable
  40. {
  41. Hashtable resources;
  42. Stream stream;
  43. public ResourceWriter (Stream stream)
  44. {
  45. if (stream == null)
  46. throw new ArgumentNullException ("stream is null");
  47. if (stream.CanWrite == false)
  48. throw new ArgumentException ("stream is not writable.");
  49. this.stream=stream;
  50. resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
  51. }
  52. public ResourceWriter (String fileName)
  53. {
  54. if (fileName == null)
  55. throw new ArgumentNullException ("fileName is null.");
  56. stream=new FileStream(fileName, FileMode.Create, FileAccess.Write);
  57. resources=new Hashtable(CaseInsensitiveHashCodeProvider.Default, CaseInsensitiveComparer.Default);
  58. }
  59. public void AddResource (string name, byte[] value)
  60. {
  61. if (name == null) {
  62. throw new ArgumentNullException ("name is null");
  63. }
  64. if (value == null) {
  65. throw new ArgumentNullException ("value is null");
  66. }
  67. if(resources==null) {
  68. throw new InvalidOperationException ("ResourceWriter has been closed");
  69. }
  70. if(resources[name]!=null) {
  71. throw new ArgumentException ("Resource already present: " + name);
  72. }
  73. resources.Add(name, value);
  74. }
  75. public void AddResource (string name, object value)
  76. {
  77. if (name == null) {
  78. throw new ArgumentNullException ("name is null");
  79. }
  80. if(resources==null) {
  81. throw new InvalidOperationException ("ResourceWriter has been closed");
  82. }
  83. if(resources[name]!=null) {
  84. throw new ArgumentException ("Resource already present: " + name);
  85. }
  86. resources.Add(name, value);
  87. }
  88. public void AddResource (string name, string value)
  89. {
  90. if (name == null) {
  91. throw new ArgumentNullException ("name is null");
  92. }
  93. if (value == null) {
  94. throw new ArgumentNullException ("value is null");
  95. }
  96. if(resources==null) {
  97. throw new InvalidOperationException ("ResourceWriter has been closed");
  98. }
  99. if(resources[name]!=null) {
  100. throw new ArgumentException ("Resource already present: " + name);
  101. }
  102. resources.Add(name, value);
  103. }
  104. public void Close () {
  105. Dispose(true);
  106. }
  107. public void Dispose ()
  108. {
  109. Dispose(true);
  110. }
  111. private void Dispose (bool disposing)
  112. {
  113. if(disposing) {
  114. if(resources.Count>0 && generated==false) {
  115. Generate();
  116. }
  117. if(stream!=null) {
  118. stream.Close();
  119. }
  120. }
  121. resources=null;
  122. stream=null;
  123. }
  124. private bool generated=false;
  125. public void Generate () {
  126. BinaryWriter writer;
  127. IFormatter formatter;
  128. if(resources==null) {
  129. throw new InvalidOperationException ("ResourceWriter has been closed");
  130. }
  131. if(generated) {
  132. throw new InvalidOperationException ("ResourceWriter can only Generate() once");
  133. }
  134. generated=true;
  135. writer=new BinaryWriter(stream, Encoding.UTF8);
  136. formatter=new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File|StreamingContextStates.Persistence));
  137. /* The ResourceManager header */
  138. writer.Write(ResourceManager.MagicNumber);
  139. writer.Write(ResourceManager.HeaderVersionNumber);
  140. /* Build the rest of the ResourceManager
  141. * header in memory, because we need to know
  142. * how long it is in advance
  143. */
  144. MemoryStream resman_stream=new MemoryStream();
  145. BinaryWriter resman=new BinaryWriter(resman_stream,
  146. Encoding.UTF8);
  147. resman.Write(typeof(ResourceReader).AssemblyQualifiedName);
  148. resman.Write(typeof(ResourceSet).AssemblyQualifiedName);
  149. /* Only space for 32 bits of header len in the
  150. * resource file format
  151. */
  152. int resman_len=(int)resman_stream.Length;
  153. writer.Write(resman_len);
  154. writer.Write(resman_stream.GetBuffer(), 0, resman_len);
  155. /* We need to build the ResourceReader name
  156. * and data sections simultaneously
  157. */
  158. MemoryStream res_name_stream=new MemoryStream();
  159. BinaryWriter res_name=new BinaryWriter(res_name_stream, Encoding.Unicode);
  160. MemoryStream res_data_stream=new MemoryStream();
  161. BinaryWriter res_data=new BinaryWriter(res_data_stream,
  162. Encoding.UTF8);
  163. /* Not sure if this is the best collection to
  164. * use, I just want an unordered list of
  165. * objects with fast lookup, but without
  166. * needing a key. (I suppose a hashtable with
  167. * key==value would work, but I need to find
  168. * the index of each item later)
  169. */
  170. ArrayList types=new ArrayList();
  171. int[] hashes=new int[resources.Count];
  172. int[] name_offsets=new int[resources.Count];
  173. int count=0;
  174. IDictionaryEnumerator res_enum=resources.GetEnumerator();
  175. while(res_enum.MoveNext()) {
  176. /* Hash the name */
  177. hashes[count]=GetHash((string)res_enum.Key);
  178. /* Record the offsets */
  179. name_offsets[count]=(int)res_name.BaseStream.Position;
  180. /* Write the name section */
  181. res_name.Write((string)res_enum.Key);
  182. res_name.Write((int)res_data.BaseStream.Position);
  183. if (res_enum.Value == null) {
  184. Write7BitEncodedInt (res_data, -1);
  185. count++;
  186. continue;
  187. }
  188. Type type=res_enum.Value.GetType();
  189. /* Keep a list of unique types */
  190. if(!types.Contains(type)) {
  191. types.Add(type);
  192. }
  193. /* Write the data section */
  194. Write7BitEncodedInt(res_data, types.IndexOf(type));
  195. /* Strangely, Char is serialized
  196. * rather than just written out
  197. */
  198. if(type==typeof(Byte)) {
  199. res_data.Write((Byte)res_enum.Value);
  200. } else if (type==typeof(Decimal)) {
  201. res_data.Write((Decimal)res_enum.Value);
  202. } else if (type==typeof(DateTime)) {
  203. res_data.Write(((DateTime)res_enum.Value).Ticks);
  204. } else if (type==typeof(Double)) {
  205. res_data.Write((Double)res_enum.Value);
  206. } else if (type==typeof(Int16)) {
  207. res_data.Write((Int16)res_enum.Value);
  208. } else if (type==typeof(Int32)) {
  209. res_data.Write((Int32)res_enum.Value);
  210. } else if (type==typeof(Int64)) {
  211. res_data.Write((Int64)res_enum.Value);
  212. } else if (type==typeof(SByte)) {
  213. res_data.Write((SByte)res_enum.Value);
  214. } else if (type==typeof(Single)) {
  215. res_data.Write((Single)res_enum.Value);
  216. } else if (type==typeof(String)) {
  217. res_data.Write((String)res_enum.Value);
  218. } else if (type==typeof(TimeSpan)) {
  219. res_data.Write(((TimeSpan)res_enum.Value).Ticks);
  220. } else if (type==typeof(UInt16)) {
  221. res_data.Write((UInt16)res_enum.Value);
  222. } else if (type==typeof(UInt32)) {
  223. res_data.Write((UInt32)res_enum.Value);
  224. } else if (type==typeof(UInt64)) {
  225. res_data.Write((UInt64)res_enum.Value);
  226. } else {
  227. /* non-intrinsic types are
  228. * serialized
  229. */
  230. formatter.Serialize(res_data.BaseStream, res_enum.Value);
  231. }
  232. count++;
  233. }
  234. /* Sort the hashes, keep the name offsets
  235. * matching up
  236. */
  237. Array.Sort(hashes, name_offsets);
  238. /* now do the ResourceReader header */
  239. writer.Write(1);
  240. writer.Write(resources.Count);
  241. writer.Write(types.Count);
  242. /* Write all of the unique types */
  243. foreach(Type type in types) {
  244. writer.Write(type.AssemblyQualifiedName);
  245. }
  246. /* Pad the next fields (hash values) on an 8
  247. * byte boundary, using the letters "PAD"
  248. */
  249. int pad_align=(int)(writer.BaseStream.Position & 7);
  250. int pad_chars=0;
  251. if(pad_align!=0) {
  252. pad_chars=8-pad_align;
  253. }
  254. for(int i=0; i<pad_chars; i++) {
  255. writer.Write((byte)"PAD"[i%3]);
  256. }
  257. /* Write the hashes */
  258. for(int i=0; i<resources.Count; i++) {
  259. writer.Write(hashes[i]);
  260. }
  261. /* and the name offsets */
  262. for(int i=0; i<resources.Count; i++) {
  263. writer.Write(name_offsets[i]);
  264. }
  265. /* Write the data section offset */
  266. int data_offset=(int)writer.BaseStream.Position +
  267. (int)res_name_stream.Length + 4;
  268. writer.Write(data_offset);
  269. /* The name section goes next */
  270. writer.Write(res_name_stream.GetBuffer(), 0,
  271. (int)res_name_stream.Length);
  272. /* The data section is last */
  273. writer.Write(res_data_stream.GetBuffer(), 0,
  274. (int)res_data_stream.Length);
  275. res_name.Close();
  276. res_data.Close();
  277. /* Don't close writer, according to the spec */
  278. writer.Flush();
  279. }
  280. private int GetHash(string name)
  281. {
  282. uint hash=5381;
  283. for(int i=0; i<name.Length; i++) {
  284. hash=((hash<<5)+hash)^name[i];
  285. }
  286. return((int)hash);
  287. }
  288. /* Cut and pasted from BinaryWriter, because it's
  289. * 'protected' there.
  290. */
  291. private void Write7BitEncodedInt(BinaryWriter writer,
  292. int value)
  293. {
  294. do {
  295. int high = (value >> 7) & 0x01ffffff;
  296. byte b = (byte)(value & 0x7f);
  297. if (high != 0) {
  298. b = (byte)(b | 0x80);
  299. }
  300. writer.Write(b);
  301. value = high;
  302. } while(value != 0);
  303. }
  304. internal Stream Stream {
  305. get {
  306. return stream;
  307. }
  308. }
  309. }
  310. }