// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Dtf.WindowsInstaller { using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Text; using System.Diagnostics.CodeAnalysis; /// /// Collection of column information related to a or /// . /// public sealed class ColumnCollection : ICollection { private IList columns; private string formatString; /// /// Creates a new ColumnCollection based on a specified list of columns. /// /// columns to be added to the new collection public ColumnCollection(ICollection columns) { if (columns == null) { throw new ArgumentNullException("columns"); } this.columns = new List(columns); } /// /// Creates a new ColumnCollection that is associated with a database table. /// /// view that contains the columns internal ColumnCollection(View view) { if (view == null) { throw new ArgumentNullException("view"); } this.columns = ColumnCollection.GetViewColumns(view); } /// /// Gets the number of columns in the collection. /// /// number of columns in the collection public int Count { get { return this.columns.Count; } } /// /// Gets a boolean value indicating whether the collection is read-only. /// A ColumnCollection is read-only if it is associated with a /// or a read-only . /// /// read-only status of the collection public bool IsReadOnly { get { return true; } } /// /// Gets information about a specific column in the collection. /// /// 1-based index into the column collection /// is less /// than 1 or greater than the number of columns in the collection public ColumnInfo this[int columnIndex] { get { if (columnIndex >= 0 && columnIndex < this.columns.Count) { return this.columns[columnIndex]; } else { throw new ArgumentOutOfRangeException("columnIndex"); } } } /// /// Gets information about a specific column in the collection. /// /// case-sensitive name of a column collection /// does /// not exist in the collection public ColumnInfo this[string columnName] { get { if (String.IsNullOrEmpty(columnName)) { throw new ArgumentNullException("columnName"); } foreach (ColumnInfo colInfo in this.columns) { if (colInfo.Name == columnName) { return colInfo; } } throw new ArgumentOutOfRangeException("columnName"); } } /// /// Not supported because the collection is read-only. /// /// information about the column being added /// the collection is read-only public void Add(ColumnInfo item) { throw new InvalidOperationException(); } /// /// Not supported because the collection is read-only. /// /// the collection is read-only public void Clear() { throw new InvalidOperationException(); } /// /// Checks if a column with a given name exists in the collection. /// /// case-sensitive name of the column to look for /// true if the column exists in the collection, false otherwise public bool Contains(string columnName) { return this.IndexOf(columnName) >= 0; } /// /// Checks if a column with a given name exists in the collection. /// /// column to look for, with case-sensitive name /// true if the column exists in the collection, false otherwise bool ICollection.Contains(ColumnInfo column) { return this.Contains(column.Name); } /// /// Gets the index of a column within the collection. /// /// case-sensitive name of the column to look for /// 0-based index of the column, or -1 if not found public int IndexOf(string columnName) { if (String.IsNullOrEmpty(columnName)) { throw new ArgumentNullException("columnName"); } for (int index = 0; index < this.columns.Count; index++) { if (this.columns[index].Name == columnName) { return index; } } return -1; } /// /// Copies the columns from this collection into an array. /// /// destination array to be filed /// offset into the destination array where copying begins public void CopyTo(ColumnInfo[] array, int arrayIndex) { if (array == null) { throw new ArgumentNullException("array"); } this.columns.CopyTo(array, arrayIndex); } /// /// Not supported because the collection is read-only. /// /// column to remove /// true if the column was removed, false if it was not found /// the collection is read-only [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "column")] [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] bool ICollection.Remove(ColumnInfo column) { throw new InvalidOperationException(); } /// /// Gets an enumerator over the columns in the collection. /// /// An enumerator of ColumnInfo objects. public IEnumerator GetEnumerator() { return this.columns.GetEnumerator(); } /// /// Gets a string suitable for printing all the values of a record containing these columns. /// public string FormatString { get { if (this.formatString == null) { this.formatString = CreateFormatString(this.columns); } return this.formatString; } } private static string CreateFormatString(IList columns) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < columns.Count; i++) { if (columns[i].Type == typeof(Stream)) { sb.AppendFormat("{0} = [Binary Data]", columns[i].Name); } else { sb.AppendFormat("{0} = [{1}]", columns[i].Name, i + 1); } if (i < columns.Count - 1) { sb.Append(", "); } } return sb.ToString(); } /// /// Gets an enumerator over the columns in the collection. /// /// An enumerator of ColumnInfo objects. IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } /// /// Creates ColumnInfo objects for the associated view. /// /// dynamically-generated list of columns private static IList GetViewColumns(View view) { IList columnNames = ColumnCollection.GetViewColumns(view, false); IList columnTypes = ColumnCollection.GetViewColumns(view, true); int count = columnNames.Count; if (columnTypes[count - 1] == "O0") { // Weird.. the "_Tables" table returns a second column with type "O0" -- ignore it. count--; } IList columnsList = new List(count); for (int i = 0; i < count; i++) { columnsList.Add(new ColumnInfo(columnNames[i], columnTypes[i])); } return columnsList; } /// /// Gets a list of column names or column-definition-strings for the /// associated view. /// /// the view to that defines the columns /// true to return types (column definition strings), /// false to return names /// list of column names or types private static IList GetViewColumns(View view, bool types) { int recordHandle; int typesFlag = types ? 1 : 0; uint ret = RemotableNativeMethods.MsiViewGetColumnInfo( (int) view.Handle, (uint) typesFlag, out recordHandle); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } using (Record rec = new Record((IntPtr) recordHandle, true, null)) { int count = rec.FieldCount; IList columnsList = new List(count); // Since we must be getting all strings of limited length, // this code is faster than calling rec.GetString(field). for (int field = 1; field <= count; field++) { uint bufSize = 256; StringBuilder buf = new StringBuilder((int) bufSize); ret = RemotableNativeMethods.MsiRecordGetString((int) rec.Handle, (uint) field, buf, ref bufSize); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } columnsList.Add(buf.ToString()); } return columnsList; } } } }