2015-12-17 15:04:18 -08:00
|
|
|
|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
|
|
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
|
|
|
|
namespace Microsoft.Extensions.DependencyModel
|
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
public class DependencyContextJsonReader : IDependencyContextReader
|
2015-12-17 15:04:18 -08:00
|
|
|
|
{
|
|
|
|
|
public DependencyContext Read(Stream stream)
|
|
|
|
|
{
|
2016-03-16 16:31:38 -07:00
|
|
|
|
if (stream == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(stream));
|
|
|
|
|
}
|
2015-12-17 15:04:18 -08:00
|
|
|
|
using (var streamReader = new StreamReader(stream))
|
|
|
|
|
{
|
|
|
|
|
using (var reader = new JsonTextReader(streamReader))
|
|
|
|
|
{
|
|
|
|
|
var root = JObject.Load(reader);
|
|
|
|
|
return Read(root);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsRuntimeTarget(string name) => name.Contains(DependencyContextStrings.VersionSeperator);
|
|
|
|
|
|
|
|
|
|
private DependencyContext Read(JObject root)
|
|
|
|
|
{
|
2016-03-04 10:57:38 -08:00
|
|
|
|
var runtime = string.Empty;
|
|
|
|
|
var target = string.Empty;
|
2016-03-04 15:05:29 -08:00
|
|
|
|
var isPortable = true;
|
2016-03-23 14:51:03 -07:00
|
|
|
|
string runtimeTargetName = null;
|
|
|
|
|
string runtimeSignature = null;
|
2016-03-04 15:05:29 -08:00
|
|
|
|
|
2016-03-23 14:51:03 -07:00
|
|
|
|
var runtimeTargetInfo = root[DependencyContextStrings.RuntimeTargetPropertyName];
|
|
|
|
|
|
|
|
|
|
// This fallback is temporary
|
|
|
|
|
if (runtimeTargetInfo is JValue)
|
|
|
|
|
{
|
|
|
|
|
runtimeTargetName = runtimeTargetInfo.Value<string>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var runtimeTargetObject = (JObject) runtimeTargetInfo;
|
|
|
|
|
runtimeTargetName = runtimeTargetObject?[DependencyContextStrings.RuntimeTargetNamePropertyName]?.Value<string>();
|
|
|
|
|
runtimeSignature = runtimeTargetObject?[DependencyContextStrings.RuntimeTargetSignaturePropertyName]?.Value<string>();
|
|
|
|
|
}
|
2016-03-01 15:11:52 -08:00
|
|
|
|
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var libraryStubs = ReadLibraryStubs((JObject)root[DependencyContextStrings.LibrariesPropertyName]);
|
|
|
|
|
var targetsObject = (JObject)root[DependencyContextStrings.TargetsPropertyName];
|
2015-12-17 15:04:18 -08:00
|
|
|
|
|
2016-03-01 15:11:52 -08:00
|
|
|
|
JObject runtimeTarget = null;
|
|
|
|
|
JObject compileTarget = null;
|
2016-03-17 08:36:37 -07:00
|
|
|
|
|
|
|
|
|
if (targetsObject == null)
|
2016-03-01 15:11:52 -08:00
|
|
|
|
{
|
2016-03-17 08:36:37 -07:00
|
|
|
|
throw new FormatException("Dependency file does not have 'targets' section");
|
|
|
|
|
}
|
2016-03-04 10:57:38 -08:00
|
|
|
|
|
2016-03-17 08:36:37 -07:00
|
|
|
|
if (!string.IsNullOrEmpty(runtimeTargetName))
|
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
runtimeTarget = (JObject)targetsObject[runtimeTargetName];
|
2016-03-17 08:36:37 -07:00
|
|
|
|
if (runtimeTarget == null)
|
|
|
|
|
{
|
|
|
|
|
throw new FormatException($"Target with name {runtimeTargetName} not found");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var runtimeTargetProperty = targetsObject.Properties()
|
|
|
|
|
.FirstOrDefault(p => IsRuntimeTarget(p.Name));
|
|
|
|
|
|
|
|
|
|
runtimeTarget = (JObject)runtimeTargetProperty?.Value;
|
|
|
|
|
runtimeTargetName = runtimeTargetProperty?.Name;
|
|
|
|
|
}
|
2016-03-01 15:11:52 -08:00
|
|
|
|
|
2016-03-17 08:36:37 -07:00
|
|
|
|
if (runtimeTargetName != null)
|
|
|
|
|
{
|
|
|
|
|
var seperatorIndex = runtimeTargetName.IndexOf(DependencyContextStrings.VersionSeperator);
|
|
|
|
|
if (seperatorIndex > -1 && seperatorIndex < runtimeTargetName.Length)
|
2016-03-01 15:11:52 -08:00
|
|
|
|
{
|
2016-03-17 08:36:37 -07:00
|
|
|
|
runtime = runtimeTargetName.Substring(seperatorIndex + 1);
|
|
|
|
|
target = runtimeTargetName.Substring(0, seperatorIndex);
|
|
|
|
|
isPortable = false;
|
2016-03-01 15:11:52 -08:00
|
|
|
|
}
|
|
|
|
|
else
|
2016-03-17 08:36:37 -07:00
|
|
|
|
{
|
|
|
|
|
target = runtimeTargetName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ridlessTargetProperty = targetsObject.Properties().FirstOrDefault(p => !IsRuntimeTarget(p.Name));
|
|
|
|
|
if (ridlessTargetProperty != null)
|
|
|
|
|
{
|
|
|
|
|
compileTarget = (JObject)ridlessTargetProperty.Value;
|
|
|
|
|
if (runtimeTarget == null)
|
2016-03-01 15:11:52 -08:00
|
|
|
|
{
|
|
|
|
|
runtimeTarget = compileTarget;
|
2016-03-17 08:36:37 -07:00
|
|
|
|
target = ridlessTargetProperty.Name;
|
2016-03-01 15:11:52 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-02 15:31:13 -08:00
|
|
|
|
|
2016-03-17 08:36:37 -07:00
|
|
|
|
if (runtimeTarget == null)
|
|
|
|
|
{
|
|
|
|
|
throw new FormatException("No runtime target found");
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-17 15:04:18 -08:00
|
|
|
|
return new DependencyContext(
|
2016-03-23 14:51:03 -07:00
|
|
|
|
new TargetInfo(target, runtime, runtimeSignature, isPortable),
|
2015-12-21 10:36:20 -08:00
|
|
|
|
ReadCompilationOptions((JObject)root[DependencyContextStrings.CompilationOptionsPropertName]),
|
2016-03-01 15:11:52 -08:00
|
|
|
|
ReadLibraries(compileTarget, false, libraryStubs).Cast<CompilationLibrary>().ToArray(),
|
|
|
|
|
ReadLibraries(runtimeTarget, true, libraryStubs).Cast<RuntimeLibrary>().ToArray(),
|
|
|
|
|
ReadRuntimeGraph((JObject)root[DependencyContextStrings.RuntimesPropertyName]).ToArray()
|
2015-12-17 15:04:18 -08:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-10 10:12:43 -08:00
|
|
|
|
private IEnumerable<RuntimeFallbacks> ReadRuntimeGraph(JObject runtimes)
|
2016-03-01 15:11:52 -08:00
|
|
|
|
{
|
|
|
|
|
if (runtimes == null)
|
|
|
|
|
{
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-15 12:51:43 -07:00
|
|
|
|
foreach (var pair in runtimes)
|
2016-03-01 15:11:52 -08:00
|
|
|
|
{
|
2016-03-10 10:12:43 -08:00
|
|
|
|
yield return new RuntimeFallbacks(pair.Key, pair.Value.Values<string>().ToArray());
|
2016-03-01 15:11:52 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-21 10:36:20 -08:00
|
|
|
|
private CompilationOptions ReadCompilationOptions(JObject compilationOptionsObject)
|
|
|
|
|
{
|
2016-03-01 15:11:52 -08:00
|
|
|
|
if (compilationOptionsObject == null)
|
|
|
|
|
{
|
|
|
|
|
return CompilationOptions.Default;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-21 10:36:20 -08:00
|
|
|
|
return new CompilationOptions(
|
2016-03-17 08:36:37 -07:00
|
|
|
|
compilationOptionsObject[DependencyContextStrings.DefinesPropertyName]?.Values<string>() ?? Enumerable.Empty<string>(),
|
2015-12-21 10:36:20 -08:00
|
|
|
|
compilationOptionsObject[DependencyContextStrings.LanguageVersionPropertyName]?.Value<string>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.PlatformPropertyName]?.Value<string>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.AllowUnsafePropertyName]?.Value<bool>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.WarningsAsErrorsPropertyName]?.Value<bool>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.OptimizePropertyName]?.Value<bool>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.KeyFilePropertyName]?.Value<string>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.DelaySignPropertyName]?.Value<bool>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.PublicSignPropertyName]?.Value<bool>(),
|
2016-03-02 15:53:59 -08:00
|
|
|
|
compilationOptionsObject[DependencyContextStrings.DebugTypePropertyName]?.Value<string>(),
|
2016-01-08 11:03:14 -08:00
|
|
|
|
compilationOptionsObject[DependencyContextStrings.EmitEntryPointPropertyName]?.Value<bool>(),
|
|
|
|
|
compilationOptionsObject[DependencyContextStrings.GenerateXmlDocumentationPropertyName]?.Value<bool>()
|
2015-12-21 10:36:20 -08:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-07 15:11:47 -08:00
|
|
|
|
private IEnumerable<Library> ReadLibraries(JObject librariesObject, bool runtime, Dictionary<string, LibraryStub> libraryStubs)
|
2015-12-17 15:04:18 -08:00
|
|
|
|
{
|
2016-03-01 15:11:52 -08:00
|
|
|
|
if (librariesObject == null)
|
|
|
|
|
{
|
|
|
|
|
return Enumerable.Empty<Library>();
|
|
|
|
|
}
|
2016-01-07 15:11:47 -08:00
|
|
|
|
return librariesObject.Properties().Select(property => ReadLibrary(property, runtime, libraryStubs));
|
2015-12-17 15:04:18 -08:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-07 15:11:47 -08:00
|
|
|
|
private Library ReadLibrary(JProperty property, bool runtime, Dictionary<string, LibraryStub> libraryStubs)
|
2015-12-17 15:04:18 -08:00
|
|
|
|
{
|
|
|
|
|
var nameWithVersion = property.Name;
|
2015-12-21 10:36:20 -08:00
|
|
|
|
LibraryStub stub;
|
2015-12-17 15:04:18 -08:00
|
|
|
|
|
|
|
|
|
if (!libraryStubs.TryGetValue(nameWithVersion, out stub))
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException($"Cannot find library information for {nameWithVersion}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var seperatorPosition = nameWithVersion.IndexOf(DependencyContextStrings.VersionSeperator);
|
|
|
|
|
|
|
|
|
|
var name = nameWithVersion.Substring(0, seperatorPosition);
|
|
|
|
|
var version = nameWithVersion.Substring(seperatorPosition + 1);
|
|
|
|
|
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var libraryObject = (JObject)property.Value;
|
2015-12-17 15:04:18 -08:00
|
|
|
|
|
|
|
|
|
var dependencies = ReadDependencies(libraryObject);
|
|
|
|
|
|
2016-01-07 15:11:47 -08:00
|
|
|
|
if (runtime)
|
|
|
|
|
{
|
2016-03-02 15:31:13 -08:00
|
|
|
|
var runtimeTargetsObject = (JObject)libraryObject[DependencyContextStrings.RuntimeTargetsPropertyName];
|
|
|
|
|
|
|
|
|
|
var entries = ReadRuntimeTargetEntries(runtimeTargetsObject).ToArray();
|
|
|
|
|
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var runtimeAssemblyGroups = new List<RuntimeAssetGroup>();
|
|
|
|
|
var nativeLibraryGroups = new List<RuntimeAssetGroup>();
|
2016-03-02 15:31:13 -08:00
|
|
|
|
foreach (var ridGroup in entries.GroupBy(e => e.Rid))
|
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var groupRuntimeAssemblies = entries.Where(e => e.Type == DependencyContextStrings.RuntimeAssetType)
|
|
|
|
|
.Select(e => e.Path)
|
2016-03-02 15:31:13 -08:00
|
|
|
|
.ToArray();
|
|
|
|
|
|
2016-03-17 11:56:57 -07:00
|
|
|
|
if (groupRuntimeAssemblies.Any())
|
|
|
|
|
{
|
|
|
|
|
runtimeAssemblyGroups.Add(new RuntimeAssetGroup(
|
|
|
|
|
ridGroup.Key,
|
|
|
|
|
groupRuntimeAssemblies.Where(a => Path.GetFileName(a) != "_._")));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var groupNativeLibraries = entries.Where(e => e.Type == DependencyContextStrings.NativeAssetType)
|
2016-03-02 15:31:13 -08:00
|
|
|
|
.Select(e => e.Path)
|
|
|
|
|
.ToArray();
|
|
|
|
|
|
2016-03-17 11:56:57 -07:00
|
|
|
|
if (groupNativeLibraries.Any())
|
|
|
|
|
{
|
|
|
|
|
nativeLibraryGroups.Add(new RuntimeAssetGroup(
|
|
|
|
|
ridGroup.Key,
|
|
|
|
|
groupNativeLibraries.Where(a => Path.GetFileName(a) != "_._")));
|
|
|
|
|
}
|
2016-03-02 15:31:13 -08:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var runtimeAssemblies = ReadAssetList(libraryObject, DependencyContextStrings.RuntimeAssembliesKey)
|
2016-03-02 15:31:13 -08:00
|
|
|
|
.ToArray();
|
2016-03-17 11:56:57 -07:00
|
|
|
|
if (runtimeAssemblies.Any())
|
|
|
|
|
{
|
|
|
|
|
runtimeAssemblyGroups.Add(new RuntimeAssetGroup(string.Empty, runtimeAssemblies));
|
|
|
|
|
}
|
2016-03-02 15:31:13 -08:00
|
|
|
|
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var nativeLibraries = ReadAssetList(libraryObject, DependencyContextStrings.NativeLibrariesKey)
|
|
|
|
|
.ToArray();
|
|
|
|
|
if(nativeLibraries.Any())
|
|
|
|
|
{
|
|
|
|
|
nativeLibraryGroups.Add(new RuntimeAssetGroup(string.Empty, nativeLibraries));
|
|
|
|
|
}
|
2016-03-11 14:49:23 -08:00
|
|
|
|
|
2016-03-07 10:51:40 -08:00
|
|
|
|
var resourceAssemblies = ReadResourceAssemblies((JObject)libraryObject[DependencyContextStrings.ResourceAssembliesPropertyName]);
|
|
|
|
|
|
|
|
|
|
return new RuntimeLibrary(
|
|
|
|
|
type: stub.Type,
|
|
|
|
|
name: name,
|
|
|
|
|
version: version,
|
|
|
|
|
hash: stub.Hash,
|
2016-03-17 11:56:57 -07:00
|
|
|
|
runtimeAssemblyGroups: runtimeAssemblyGroups,
|
|
|
|
|
nativeLibraryGroups: nativeLibraryGroups,
|
2016-03-07 10:51:40 -08:00
|
|
|
|
resourceAssemblies: resourceAssemblies,
|
|
|
|
|
dependencies: dependencies,
|
|
|
|
|
serviceable: stub.Serviceable);
|
2016-01-07 15:11:47 -08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-03-11 14:49:23 -08:00
|
|
|
|
var assemblies = ReadAssetList(libraryObject, DependencyContextStrings.CompileTimeAssembliesKey);
|
2016-01-07 15:11:47 -08:00
|
|
|
|
return new CompilationLibrary(stub.Type, name, version, stub.Hash, assemblies, dependencies, stub.Serviceable);
|
|
|
|
|
}
|
2015-12-17 15:04:18 -08:00
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 10:51:40 -08:00
|
|
|
|
private IEnumerable<ResourceAssembly> ReadResourceAssemblies(JObject resourcesObject)
|
|
|
|
|
{
|
|
|
|
|
if (resourcesObject == null)
|
|
|
|
|
{
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
foreach (var resourceProperty in resourcesObject)
|
|
|
|
|
{
|
|
|
|
|
yield return new ResourceAssembly(
|
|
|
|
|
locale: resourceProperty.Value[DependencyContextStrings.LocalePropertyName]?.Value<string>(),
|
|
|
|
|
path: resourceProperty.Key
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-02 15:31:13 -08:00
|
|
|
|
private static IEnumerable<RuntimeTargetEntryStub> ReadRuntimeTargetEntries(JObject runtimeTargetObject)
|
2015-12-17 15:04:18 -08:00
|
|
|
|
{
|
2016-03-02 15:31:13 -08:00
|
|
|
|
if (runtimeTargetObject == null)
|
|
|
|
|
{
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
foreach (var libraryProperty in runtimeTargetObject)
|
|
|
|
|
{
|
|
|
|
|
var libraryObject = (JObject)libraryProperty.Value;
|
|
|
|
|
yield return new RuntimeTargetEntryStub()
|
|
|
|
|
{
|
|
|
|
|
Path = libraryProperty.Key,
|
|
|
|
|
Rid = libraryObject[DependencyContextStrings.RidPropertyName].Value<string>(),
|
|
|
|
|
Type = libraryObject[DependencyContextStrings.AssetTypePropertyName].Value<string>()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-11 14:49:23 -08:00
|
|
|
|
private static string[] ReadAssetList(JObject libraryObject, string name)
|
2016-03-02 15:31:13 -08:00
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var assembliesObject = (JObject)libraryObject[name];
|
2015-12-17 15:04:18 -08:00
|
|
|
|
|
|
|
|
|
if (assembliesObject == null)
|
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
return new string[] { };
|
2015-12-17 15:04:18 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return assembliesObject.Properties().Select(property => property.Name).ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Dependency[] ReadDependencies(JObject libraryObject)
|
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var dependenciesObject = (JObject)libraryObject[DependencyContextStrings.DependenciesPropertyName];
|
2015-12-17 15:04:18 -08:00
|
|
|
|
|
|
|
|
|
if (dependenciesObject == null)
|
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
return new Dependency[] { };
|
2015-12-17 15:04:18 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dependenciesObject.Properties()
|
2016-03-17 11:56:57 -07:00
|
|
|
|
.Select(property => new Dependency(property.Name, (string)property.Value)).ToArray();
|
2015-12-17 15:04:18 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Dictionary<string, LibraryStub> ReadLibraryStubs(JObject librariesObject)
|
|
|
|
|
{
|
|
|
|
|
var libraries = new Dictionary<string, LibraryStub>();
|
2016-03-01 15:11:52 -08:00
|
|
|
|
if (librariesObject != null)
|
2015-12-17 15:04:18 -08:00
|
|
|
|
{
|
2016-03-01 15:11:52 -08:00
|
|
|
|
foreach (var libraryProperty in librariesObject)
|
2015-12-17 15:04:18 -08:00
|
|
|
|
{
|
2016-03-17 11:56:57 -07:00
|
|
|
|
var value = (JObject)libraryProperty.Value;
|
2016-03-01 15:11:52 -08:00
|
|
|
|
var stub = new LibraryStub
|
|
|
|
|
{
|
|
|
|
|
Name = libraryProperty.Key,
|
|
|
|
|
Hash = value[DependencyContextStrings.Sha512PropertyName]?.Value<string>(),
|
|
|
|
|
Type = value[DependencyContextStrings.TypePropertyName].Value<string>(),
|
|
|
|
|
Serviceable = value[DependencyContextStrings.ServiceablePropertyName]?.Value<bool>() == true
|
|
|
|
|
};
|
|
|
|
|
libraries.Add(stub.Name, stub);
|
|
|
|
|
}
|
2015-12-17 15:04:18 -08:00
|
|
|
|
}
|
|
|
|
|
return libraries;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-02 15:31:13 -08:00
|
|
|
|
private struct RuntimeTargetEntryStub
|
|
|
|
|
{
|
|
|
|
|
public string Type;
|
|
|
|
|
public string Path;
|
|
|
|
|
public string Rid;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-17 15:04:18 -08:00
|
|
|
|
private struct LibraryStub
|
|
|
|
|
{
|
|
|
|
|
public string Name;
|
|
|
|
|
|
|
|
|
|
public string Hash;
|
|
|
|
|
|
|
|
|
|
public string Type;
|
|
|
|
|
|
|
|
|
|
public bool Serviceable;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-02 15:53:59 -08:00
|
|
|
|
}
|