// 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;
}
}
}
}