1 //------------------------------------------------------------------------------
2 // <copyright file="BitmapSelector.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Drawing {
8     using System;
9     using System.Configuration;
10     using System.Drawing.Configuration;
11     using System.IO;
12     using System.Reflection;
13 
14     /// <summary>
15     /// Provides methods to select from multiple bitmaps depending on a "bitmapSuffix" config setting.
16     /// </summary>
17     internal static class BitmapSelector {
18 
19         /// <summary>
20         /// Gets the bitmap ID suffix defined in the application configuration, or string.Empty if
21         /// the suffix is not specified.  Internal for unit tests
22         /// </summary>
23         /// <remarks>
24         /// For performance, the suffix is cached in a static variable so it only has to be read
25         /// once per appdomain.
26         /// </remarks>
27         private static string _suffix;
28         internal static string Suffix {
29             get {
30                 if (_suffix == null) {
31                     _suffix = string.Empty;
32                     var section = ConfigurationManager.GetSection("system.drawing") as SystemDrawingSection;
33                     if (section != null) {
34                         var value = section.BitmapSuffix;
35                         if (value != null && value is string) {
36                             _suffix = (string)value;
37                         }
38                     }
39                 }
40                 return _suffix;
41             }
42             set {
43                 // So unit tests can clear the cached suffix
44                 _suffix = value;
45             }
46         }
47 
48         /// <summary>
49         /// Appends the current suffix to <paramref name="filePath"/>.  The suffix is appended
50         /// before the existing extension (if any).  Internal for unit tests.
51         /// </summary>
52         /// <returns>
53         /// The new path with the suffix included.  If there is no suffix defined or there are
54         /// invalid characters in the original path, the original path is returned.
55         /// </returns>
AppendSuffix(string filePath)56         internal static string AppendSuffix(string filePath) {
57             try {
58                 return Path.ChangeExtension(filePath, Suffix + Path.GetExtension(filePath));
59             }
60             catch (ArgumentException) { // there are invalid characters in the path
61                 return filePath;
62             }
63         }
64 
65         /// <summary>
66         /// Returns <paramref name="originalPath"/> with the current suffix appended (before the
67         /// existing extension) if the resulting file path exists; otherwise the original path is
68         /// returned.
69         /// </summary>
GetFileName(string originalPath)70         public static string GetFileName(string originalPath) {
71             if (Suffix == string.Empty)
72                 return originalPath;
73 
74             string newPath = AppendSuffix(originalPath);
75             return File.Exists(newPath) ? newPath : originalPath;
76         }
77 
78         // Calls assembly.GetManifestResourceStream in a try/catch and returns null if not found
GetResourceStreamHelper(Assembly assembly, Type type, string name)79         private static Stream GetResourceStreamHelper(Assembly assembly, Type type, string name) {
80             Stream stream = null;
81             try {
82                 stream = assembly.GetManifestResourceStream(type, name);
83             }
84             catch (FileNotFoundException) {
85             }
86             return stream;
87         }
88 
DoesAssemblyHaveCustomAttribute(Assembly assembly, string typeName)89         private static bool DoesAssemblyHaveCustomAttribute(Assembly assembly, string typeName) {
90             return DoesAssemblyHaveCustomAttribute(assembly, assembly.GetType(typeName));
91         }
92 
DoesAssemblyHaveCustomAttribute(Assembly assembly, Type attrType)93         private static bool DoesAssemblyHaveCustomAttribute(Assembly assembly, Type attrType) {
94             if (attrType != null) {
95                 var attr = assembly.GetCustomAttributes(attrType, false);
96                 if (attr.Length > 0) {
97                     return true;
98                 }
99             }
100             return false;
101         }
102 
103         // internal for unit tests
SatelliteAssemblyOptIn(Assembly assembly)104         internal static bool SatelliteAssemblyOptIn(Assembly assembly) {
105             // Try 4.5 public attribute type first
106             if (DoesAssemblyHaveCustomAttribute(assembly, typeof(BitmapSuffixInSatelliteAssemblyAttribute))) {
107                 return true;
108             }
109 
110             // Also load attribute type by name for dlls compiled against older frameworks
111             return DoesAssemblyHaveCustomAttribute(assembly, "System.Drawing.BitmapSuffixInSatelliteAssemblyAttribute");
112         }
113 
114         // internal for unit tests
SameAssemblyOptIn(Assembly assembly)115         internal static bool SameAssemblyOptIn(Assembly assembly) {
116             // Try 4.5 public attribute type first
117             if (DoesAssemblyHaveCustomAttribute(assembly, typeof(BitmapSuffixInSameAssemblyAttribute))) {
118                 return true;
119             }
120 
121             // Also load attribute type by name for dlls compiled against older frameworks
122             return DoesAssemblyHaveCustomAttribute(assembly, "System.Drawing.BitmapSuffixInSameAssemblyAttribute");
123         }
124 
125         /// <summary>
126         /// Returns a resource stream loaded from the appropriate location according to the current
127         /// suffix.
128         /// </summary>
129         /// <param name="assembly">The assembly from which the stream is loaded</param>
130         /// <param name="type">The type whose namespace is used to scope the manifest resource name</param>
131         /// <param name="originalName">The name of the manifest resource being requested</param>
132         /// <returns>
133         /// The manifest resource stream corresponding to <paramref name="originalName"/> with the
134         /// current suffix applied; or if that is not found, the stream corresponding to <paramref name="originalName"/>.
135         /// </returns>
GetResourceStream(Assembly assembly, Type type, string originalName)136         public static Stream GetResourceStream(Assembly assembly, Type type, string originalName) {
137             if (Suffix != string.Empty) {
138                 try {
139                     // Resource with suffix has highest priority
140                     if (SameAssemblyOptIn(assembly)) {
141                         string newName = AppendSuffix(originalName);
142                         Stream stream = GetResourceStreamHelper(assembly, type, newName);
143                         if (stream != null) {
144                             return stream;
145                         }
146                     }
147                 }
148                 catch {
149                     // Ignore failures and continue to try other options
150                 }
151 
152                 try {
153                     // Satellite assembly has second priority, using the original name
154                     if (SatelliteAssemblyOptIn(assembly)) {
155                         AssemblyName assemblyName = assembly.GetName();
156                         assemblyName.Name += Suffix;
157                         assemblyName.ProcessorArchitecture = ProcessorArchitecture.None;
158                         Assembly satellite = Assembly.Load(assemblyName);
159                         if (satellite != null) {
160                             Stream stream = GetResourceStreamHelper(satellite, type, originalName);
161                             if (stream != null) {
162                                 return stream;
163                             }
164                         }
165                     }
166                 }
167                 catch {
168                     // Ignore failures and continue to try other options
169                 }
170             }
171 
172             // Otherwise fall back to specified assembly and original name requested
173             return assembly.GetManifestResourceStream(type, originalName);
174         }
175 
176         /// <summary>
177         /// Returns a resource stream loaded from the appropriate location according to the current
178         /// suffix.
179         /// </summary>
180         /// <param name="type">The type from whose assembly the stream is loaded and whose namespace is used to scope the resource name</param>
181         /// <param name="originalName">The name of the manifest resource being requested</param>
182         /// <returns>
183         /// The manifest resource stream corresponding to <paramref name="originalName"/> with the
184         /// current suffix applied; or if that is not found, the stream corresponding to <paramref name="originalName"/>.
185         /// </returns>
GetResourceStream(Type type, string originalName)186         public static Stream GetResourceStream(Type type, string originalName) {
187             return GetResourceStream(type.Module.Assembly, type, originalName);
188         }
189 
190         /// <summary>
191         /// Returns an Icon created  from a resource stream loaded from the appropriate location according to the current
192         /// suffix.
193         /// </summary>
194         /// <param name="type">The type from whose assembly the stream is loaded and whose namespace is used to scope the resource name</param>
195         /// <param name="originalName">The name of the manifest resource being requested</param>
196         /// <returns>
197         /// The icon created from a manifest resource stream corresponding to <paramref name="originalName"/> with the
198         /// current suffix applied; or if that is not found, the stream corresponding to <paramref name="originalName"/>.
199         /// </returns>
CreateIcon(Type type, string originalName)200         public static Icon CreateIcon(Type type, string originalName) {
201             return new Icon(GetResourceStream(type, originalName));
202         }
203 
204         /// <summary>
205         /// Returns an Bitmap created  from a resource stream loaded from the appropriate location according to the current
206         /// suffix.
207         /// </summary>
208         /// <param name="type">The type from whose assembly the stream is loaded and whose namespace is used to scope the resource name</param>
209         /// <param name="originalName">The name of the manifest resource being requested</param>
210         /// <returns>
211         /// The bitmap created from a manifest resource stream corresponding to <paramref name="originalName"/> with the
212         /// current suffix applied; or if that is not found, the stream corresponding to <paramref name="originalName"/>.
213         /// </returns>
CreateBitmap(Type type, string originalName)214         public static Bitmap CreateBitmap(Type type, string originalName) {
215             return new Bitmap(GetResourceStream(type, originalName));
216         }
217 
218     }
219 }
220