using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Localization.Metadata;
namespace UnityEngine.Localization.Tables
{
///
/// Player version of a table entry that can contain additional data that is not serialized.
///
public class TableEntry : IMetadataCollection
{
SharedTableData.SharedTableEntry m_SharedTableEntry;
///
/// The table that this entry is part of.
///
public LocalizationTable Table { get; internal set; }
///
/// The serialized data
///
internal TableEntryData Data { get; set; }
///
/// The shared table entry contains information for all locales, this is taken from .
///
public SharedTableData.SharedTableEntry SharedEntry
{
get
{
if (m_SharedTableEntry == null)
{
Assertions.Assert.IsNotNull(Table);
m_SharedTableEntry = Table.SharedData.GetEntry(KeyId);
}
return m_SharedTableEntry;
}
}
///
/// The Key or Name of this table entry that is stored in .
///
public string Key
{
get => SharedEntry?.Key;
set => Table.SharedData.RenameKey(KeyId, value);
}
///
/// Key Id for this table entry.
///
public long KeyId => Data.Id;
///
/// Raw localized value.
///
public string LocalizedValue => Data.Localized;
///
/// The Metadata for this table entry.
///
public IList MetadataEntries => Data.Metadata.MetadataEntries;
///
/// Returns the first Metadata item from of type TObject.
///
///
///
public TObject GetMetadata() where TObject : IMetadata
{
return Data.Metadata.GetMetadata();
}
///
/// Populates the list with all Metadata from that is of type TObject.
///
///
///
public void GetMetadatas(IList foundItems) where TObject : IMetadata
{
Data.Metadata.GetMetadatas(foundItems);
}
///
/// Returns all Metadata from that is of type TObject.
///
///
///
public IList GetMetadatas() where TObject : IMetadata
{
return Data.Metadata.GetMetadatas();
}
///
/// Returns true if any tag metadata of type TShared contains this entry.
///
///
///
public bool HasTagMetadata() where TShared : SharedTableEntryMetadata
{
var tag = Table.GetMetadata();
return tag?.IsRegistered(this) == true;
}
///
/// Tags are Metadata that can be shared across multiple table entries,
/// they are often used to indicate an entry has a particular attribute or feature, e.g SmartFormat.
/// Generally Tags do not contains data, for sharing data across multiple table entries see .
/// A Tag reference will be stored in and .
///
///
public void AddTagMetadata() where TShared : SharedTableEntryMetadata, new()
{
TShared tag = null;
foreach (var md in Table.MetadataEntries)
{
if (md is TShared shared)
{
tag = shared;
// If we already have the tag then there is nothing we need to do. (LOC-779)
if (tag.IsRegistered(this))
return;
break;
}
}
if (tag == null)
{
tag = new TShared();
Table.AddMetadata(tag);
}
tag.Register(this);
AddMetadata(tag);
}
///
/// SharedTableEntryMetadata is Metadata that can be shared across multiple entries in a single table.
/// The instance reference will be stored in and .
///
///
public void AddSharedMetadata(SharedTableEntryMetadata md)
{
if (!Table.Contains(md))
{
Table.AddMetadata(md);
}
// If we already have the tag then there is nothing we need to do.
if (md.IsRegistered(this))
return;
md.Register(this);
AddMetadata(md);
}
///
/// SharedTableCollectionMetadata is Metadata that can be applied to multiple table entries in a table collection.
/// The Metadata is stored in the .
///
///
public void AddSharedMetadata(SharedTableCollectionMetadata md)
{
if (!Table.SharedData.Metadata.Contains(md))
{
Table.SharedData.Metadata.AddMetadata(md);
}
md.AddEntry(Data.Id, Table.LocaleIdentifier.Code);
}
///
/// Add an entry to .
///
///
public void AddMetadata(IMetadata md)
{
Data.Metadata.AddMetadata(md);
}
///
/// Removes the Metadata tag from this entry and the table if it is no longer used by any other table entries.
///
///
public void RemoveTagMetadata() where TShared : SharedTableEntryMetadata
{
var tableMetada = Table.MetadataEntries;
var entryMetadata = Data.Metadata.MetadataEntries;
// We check both the entry and table metadata as we had some bugs in the past that caused them to go out of sync. (LOC-779)
// Check entry
for (int i = entryMetadata.Count - 1; i >= 0; --i)
{
if (entryMetadata[i] is TShared tag)
{
tag.Unregister(this);
entryMetadata.RemoveAt(i);
}
}
// Check table
for (int i = tableMetada.Count - 1; i >= 0; --i)
{
if (tableMetada[i] is TShared tag)
{
tag.Unregister(this);
// Remove the shared data if it is no longer used
if (tag.Count == 0)
{
tableMetada.RemoveAt(i);
}
}
}
}
///
/// Removes the entry from the shared Metadata in the table and removes the
/// shared Metadata if no other entries are using it.
///
///
public void RemoveSharedMetadata(SharedTableEntryMetadata md)
{
md.Unregister(this);
RemoveMetadata(md);
// Remove the shared data if it is no longer used
if (md.Count == 0 && Table.Contains(md))
{
Table.RemoveMetadata(md);
}
}
///
/// Removes the entry from the Shared Metadata and removes it from the
/// if no other entries are using it.
///
///
public void RemoveSharedMetadata(SharedTableCollectionMetadata md)
{
md.RemoveEntry(Data.Id, Table.LocaleIdentifier.Code);
if (md.IsEmpty)
{
Table.SharedData.Metadata.RemoveMetadata(md);
}
}
///
/// Remove an entry from .
///
///
///
public bool RemoveMetadata(IMetadata md)
{
return Data.Metadata.RemoveMetadata(md);
}
///
/// Checks if the Metadata is contained within .
///
///
///
public bool Contains(IMetadata md)
{
return Data.Metadata.Contains(md);
}
public override string ToString() => $"{KeyId} - {LocalizedValue}";
};
///
/// Options for how to handle missing entries when using .
///
public enum MissingEntryAction
{
///
/// Do nothing.
///
Nothing,
///
/// Add the missing entries to the .
///
AddEntriesToSharedData,
///
/// Remove the missing entries from the table.
///
RemoveEntriesFromTable
}
///
/// Provides common functionality for both string and asset tables.
///
///
public abstract class DetailedLocalizationTable : LocalizationTable, IDictionary, ISerializationCallbackReceiver where TEntry : TableEntry
{
Dictionary m_TableEntries = new Dictionary();
ICollection IDictionary.Keys => m_TableEntries.Keys;
///
/// All values in this table.
///
public ICollection Values => m_TableEntries.Values;
///
/// The number of entries in this Table.
///
public int Count => m_TableEntries.Count;
///
/// Will always be false. Implemented because it is required by the System.Collections.IList interface.
///
public bool IsReadOnly => false;
///
/// Get/Set a value using the specified key.
///
///
///
public TEntry this[long key]
{
get => m_TableEntries[key];
set
{
if (key == SharedTableData.EmptyId)
throw new ArgumentException("Key Id value 0, is not valid. All Key Id's must be non-zero.");
if (value.Table != this)
throw new ArgumentException("Table entry does not belong to this table. Table entries can not be shared across tables.");
// Move the entry
RemoveEntry(value.Data.Id);
value.Data.Id = key;
m_TableEntries[key] = value;
}
}
///
/// Get/Set a value using the specified key name.
///
///
///
public TEntry this[string keyName]
{
get => GetEntry(keyName);
set
{
if (value.Table != this)
throw new ArgumentException("Table entry does not belong to this table. Table entries can not be shared across tables.");
var key = FindKeyId(keyName, true);
this[key] = value;
}
}
///
/// Returns a new instance of TEntry.
///
///
public abstract TEntry CreateTableEntry();
internal TEntry CreateTableEntry(TableEntryData data)
{
var entry = CreateTableEntry();
entry.Data = data;
return entry;
}
///
public override void CreateEmpty(TableEntryReference entryReference)
{
AddEntryFromReference(entryReference, string.Empty);
}
///
/// Add or update an entry in the table.
///
/// The name of the key.
/// The localized item, a string for or asset guid for .
///
public TEntry AddEntry(string key, string localized)
{
var keyId = FindKeyId(key, true);
return keyId == 0 ? null : AddEntry(keyId, localized);
}
///
/// Add or update an entry in the table.
///
/// The unique key id.
/// The localized item, a string for or asset guid for .
///
public virtual TEntry AddEntry(long keyId, string localized)
{
if (keyId == SharedTableData.EmptyId)
throw new ArgumentException($"Key Id value {nameof(SharedTableData.EmptyId)}({SharedTableData.EmptyId}), is not valid. All Key Id's must be non-zero.", nameof(keyId));
if (!m_TableEntries.TryGetValue(keyId, out var tableEntry))
{
tableEntry = CreateTableEntry();
tableEntry.Data = new TableEntryData(keyId);
m_TableEntries[keyId] = tableEntry;
}
tableEntry.Data.Localized = localized;
return tableEntry;
}
///
/// Add or update an entry in the table.
///
/// The containing a valid Key or Key Id.
/// The localized item, a string for or asset guid for
///
public TEntry AddEntryFromReference(TableEntryReference entryReference, string localized)
{
if (entryReference.ReferenceType == TableEntryReference.Type.Id)
return AddEntry(entryReference.KeyId, localized);
if (entryReference.ReferenceType == TableEntryReference.Type.Name)
return AddEntry(entryReference.Key, localized);
throw new ArgumentException($"{nameof(TableEntryReference)} should not be Empty", nameof(entryReference));
}
///
/// Remove an entry from the table if it exists.
///
/// The name of the key.
/// True if the entry was found and removed.
public bool RemoveEntry(string key)
{
var keyId = FindKeyId(key, false);
return keyId != 0 && RemoveEntry(keyId);
}
///
/// Remove an entry from the table if it exists.
///
/// The key id to remove.
/// True if the entry was found and removed.
public virtual bool RemoveEntry(long keyId)
{
if (m_TableEntries.TryGetValue(keyId, out var item))
{
// We also need to remove any references to this entry in shared metadata.
for (int i = 0; i < MetadataEntries.Count; ++i)
{
var metadataEntry = MetadataEntries[i];
if (metadataEntry is SharedTableEntryMetadata sharedMetadata)
{
sharedMetadata.Unregister(item);
// Remove the shared data if it is no longer used
if (sharedMetadata.Count == 0)
{
MetadataEntries.RemoveAt(i);
i--;
}
}
}
for (int i = 0; i < SharedData?.Metadata.MetadataEntries.Count; i++)
{
var metadata = SharedData.Metadata.MetadataEntries[i];
if (metadata is SharedTableCollectionMetadata sharedMetadata)
{
sharedMetadata.RemoveEntry(keyId, LocaleIdentifier.Code);
// Remove the shared data if it is no longer used
if (sharedMetadata.IsEmpty)
{
SharedData.Metadata.MetadataEntries.RemoveAt(i);
i--;
}
}
}
item.Data.Id = SharedTableData.EmptyId;
item.Table = null;
return m_TableEntries.Remove(keyId);
}
return false;
}
///
/// Returns the entry reference or null if one does not exist.
///
///
///
public TEntry GetEntryFromReference(TableEntryReference entryReference)
{
if (entryReference.ReferenceType == TableEntryReference.Type.Id)
return GetEntry(entryReference.KeyId);
else if (entryReference.ReferenceType == TableEntryReference.Type.Name)
return GetEntry(entryReference.Key);
return null;
}
///
/// Returns the entry for the key or null if one does not exist.
///
///
///
public TEntry GetEntry(string key)
{
var keyId = FindKeyId(key, false);
return keyId == 0 ? null : GetEntry(keyId);
}
///
/// Returns the entry for the key id or null if one does not exist.
///
///
///
public virtual TEntry GetEntry(long keyId)
{
m_TableEntries.TryGetValue(keyId, out var tableEntry);
return tableEntry;
}
///
/// Adds the entry with the specified keyId.
///
///
///
public void Add(long keyId, TEntry value)
{
this[keyId] = value;
}
///
/// Adds the item value with the specified keyId.
///
///
public void Add(KeyValuePair item)
{
this[item.Key] = item.Value;
}
///
/// Returns true if the table contains an entry with the keyId.
///
///
///
public bool ContainsKey(long keyId) => m_TableEntries.ContainsKey(keyId);
///
/// Returns true if the table contains an entry with the same value.
///
/// The value to check for in all table entries.
/// True if a match was found else false.
public bool ContainsValue(string localized)
{
foreach (var entry in m_TableEntries.Values)
{
if (entry.Data.Localized == localized)
return true;
}
return false;
}
///
/// Returns true if the table contains the item.
///
///
///
public bool Contains(KeyValuePair item) => m_TableEntries.Contains(item);
///
/// Remove the entry with the keyId.
///
///
///
public bool Remove(long keyId) => RemoveEntry(keyId);
///
/// Remove the item from the table if it exists.
///
///
///
public bool Remove(KeyValuePair item)
{
if (Contains(item))
{
RemoveEntry(item.Key);
return true;
}
return false;
}
///
/// Tables do not store the full information for an entry, instead they store just the Id of that entry which can then be referenced in .
/// It is possible that something may have caused an entry to be in the Table but missing from .
/// This will cause issues and often result in the entry being ignored. This will check for any entries that exist in the table but do not have an entry in .
///
/// The action to take on the found missing entries.
/// The identified missing entries.
public IList CheckForMissingSharedTableDataEntries(MissingEntryAction action = MissingEntryAction.Nothing)
{
// Find all entries that are missing from the Shared Table Data.
var results = m_TableEntries.Where(e => !SharedData.Contains(e.Key)).Select(e => e.Value).ToArray();
if (results.Length == 0)
return results;
if (action == MissingEntryAction.AddEntriesToSharedData)
{
for (int i = 0; i < results.Length; ++i)
{
// Add a default key, then remap the id
var sharedEntry = SharedData.AddKey();
SharedData.RemapId(sharedEntry.Id, results[i].KeyId);
}
}
else if (action == MissingEntryAction.RemoveEntriesFromTable)
{
for (int i = 0; i < results.Length; ++i)
{
RemoveEntry(results[i].KeyId);
}
}
return results;
}
///
/// Find the entry, if it exists in the table.
///
///
///
/// True if the entry was found.
public bool TryGetValue(long keyId, out TEntry value) => m_TableEntries.TryGetValue(keyId, out value);
///
/// Clear all entries in this table.
///
public void Clear()
{
TableData.Clear();
m_TableEntries.Clear();
}
///
/// Copies the contents of the table into an array starting at the arrayIndex.
///
///
///
public void CopyTo(KeyValuePair[] array, int arrayIndex)
{
foreach (var entry in m_TableEntries)
{
array[arrayIndex++] = entry;
}
}
///
/// Return an enumerator for the entries in this table.
///
///
public IEnumerator> GetEnumerator() => m_TableEntries.GetEnumerator();
///
/// Return an enumerator for the entries in this table.
///
///
IEnumerator IEnumerable.GetEnumerator() => m_TableEntries.GetEnumerator();
///
/// Creates a string representation of the table as "{TableCollectionName}({LocaleIdentifier})".
///
///
public override string ToString() => $"{TableCollectionName}({LocaleIdentifier})";
///
/// Does nothing but required for .
///
public void OnBeforeSerialize()
{
TableData.Clear();
foreach (var entry in this)
{
// Sync the id
entry.Value.Data.Id = entry.Key;
TableData.Add(entry.Value.Data);
}
}
///
/// Converts the serialized data into .
///
public void OnAfterDeserialize()
{
try
{
m_TableEntries = TableData.ToDictionary(o => o.Id, CreateTableEntry);
}
catch (Exception e)
{
var error = $"Error Deserializing Table Data \"{TableCollectionName}({LocaleIdentifier})\".\n{e.Message}\n{e.InnerException}";
Debug.LogError(error, this);
}
}
}
}