using Json.LitJson;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
namespace ServiceClientGenerator
{
#region Simplifications
#region SimpleConstructorsModel
///
/// A model that represents the simple constructors of a structure. These constructors are specified in customizations
/// Simple constructors are constructors that are added to request objects that take parameters to set members of the object.
/// This allows for easier creation of these request objects.
///
public class SimpleConstructorsModel
{
public const string ConstructorsKey = "constructors";
JsonData _documentRoot;
readonly bool _emptyData = false;
///
/// Creates a model for simple constructors for the structure. Used to customize the generation of the structure
///
/// The json data that contains information about the customization
public SimpleConstructorsModel(JsonData root)
{
if (root.PropertyNames.Contains("simpleConstructors", StringComparer.OrdinalIgnoreCase))
{
_documentRoot = root["simpleConstructors"];
}
else
{
_documentRoot = new JsonData();
_emptyData = true;
}
}
///
/// Sets the document root from a json text
///
/// The reader to get the json text from
void Initialize(TextReader reader)
{
this._documentRoot = JsonMapper.ToObject(reader);
}
///
/// A dictionary that maps structure names to the form of the constructor
///
public Dictionary SimpleConstructors
{
get
{
var constructors = new Dictionary(StringComparer.OrdinalIgnoreCase);
var data = _documentRoot[ConstructorsKey];
if (data == null) return constructors;
foreach (KeyValuePair kvp in data)
{
constructors[kvp.Key] = new ConstructorForms(kvp.Key, kvp.Value);
}
return constructors;
}
}
///
/// Determines if custom constructors need to be generated for the structure
///
/// The name of the structure to check for
/// True if there are custom simple constructors
public bool CreateSimpleConstructors(string className)
{
if (_emptyData)
return false;
return SimpleConstructors.ContainsKey(className);
}
///
/// A list of members for the structure that are used in the constructor form
///
/// A list of the member names used by the constructor form
/// All of the members for the structure
/// The members for the form
public List GetFormMembers(List form, IList operationMembers)
{
var docMembers = new List();
foreach (var mem in form)
{
foreach (var opmem in operationMembers)
{
if (opmem.PropertyName.Equals(mem, StringComparison.OrdinalIgnoreCase))
{
docMembers.Add(opmem);
break;
}
}
}
return docMembers;
}
///
/// Used to generate a string used to define the params for the constructor
///
/// List of member names for the constructor form
/// All of the members of the structure
/// A string that represents the required params for the constructor form
public string GetSimpleParameters(List form, IList operationMembers)
{
string currentParams = "";
foreach (var mem in form)
{
Member member = null;
foreach (var opmem in operationMembers)
{
if (opmem.PropertyName.Equals(mem, StringComparison.OrdinalIgnoreCase))
{
member = opmem;
break;
}
}
if (member == null)
throw new ArgumentOutOfRangeException("Unable to find matching member");
if (currentParams == "")
currentParams = member.DetermineType() + " " + GeneratorHelpers.CamelCaseParam(member.PropertyName);
else
currentParams = currentParams + ", " + member.DetermineType() + " " + GeneratorHelpers.CamelCaseParam(member.PropertyName);
}
return currentParams;
}
}
#endregion
#region ConstructorForms
///
/// Represents forms of the constructor
/// The constructor forms are the different parameter options of a constructor
///
public class ConstructorForms
{
public const string ConstructorFormsKey = "constructorForms";
readonly JsonData _constructorRoot;
readonly string _name;
public string Name { get { return _name; } }
///
/// Creates a representation for a constructor in the structure consisting of a list of a list of member names
///
/// The name of the constructor
/// The json customization for the constructor forms
public ConstructorForms(string name, JsonData json)
{
this._name = name;
this._constructorRoot = json;
}
///
/// A list of forms for the constructor consisting of the names of members in each form
///
public List> Forms
{
get
{
var forms = new List>();
var data = _constructorRoot[ConstructorFormsKey];
if (data == null || !data.IsArray) return forms;
foreach (var formData in data)
{
var form = new List();
foreach (var item in (formData as JsonData))
{
form.Add(item.ToString());
}
forms.Add(form);
}
return forms;
}
}
}
#endregion
#region SimpleMethodFormsModel
///
/// Represents custom simple methods for the client being generated from the service model
/// Simple methods are methods added to the client that take members of the request as parameters and then creates a request with those
/// members internally and then calls the operation
///
public class SimpleMethodFormsModel
{
public const string SimpleMethodsKey = "simpleMethods";
readonly JsonData _documentRoot;
readonly bool _emptyData = false;
///
/// Creates a representation of the simple method customizations for the service client
///
/// The json data that specifies forms of the simple methods
public SimpleMethodFormsModel(JsonData root)
{
if (root.PropertyNames.Contains(SimpleMethodsKey, StringComparer.OrdinalIgnoreCase))
{
_documentRoot = root[SimpleMethodsKey];
}
else
{
_documentRoot = new JsonData();
_emptyData = true;
}
}
///
/// A mapping of method names to the forms for that simple method
///
public Dictionary SimpleMethods
{
get
{
var methods = new Dictionary(StringComparer.OrdinalIgnoreCase);
var data = _documentRoot[ServiceModel.OperationsKey];
if (data == null) return methods;
foreach (KeyValuePair kvp in data)
{
methods[kvp.Key] = new MethodForms(kvp.Key, kvp.Value);
}
return methods;
}
}
///
/// Determines if the operation in the client has a custom simple method specified
///
/// The name of the operation
/// If the operation is specified in the customizations
public bool CreateSimpleMethods(string operationName)
{
if (_emptyData)
return false;
return SimpleMethods.ContainsKey(operationName);
}
///
/// Finds the members from the request that need to be used in the method
///
/// List of member names for the given form of an operation
/// All of the members for the given operation
///
public List GetFormMembers(List form, IList operationMembers)
{
var docMembers = new List();
foreach (var mem in form)
{
foreach (var opmem in operationMembers)
{
if (opmem.PropertyName.Equals(mem, StringComparison.OrdinalIgnoreCase))
{
docMembers.Add(opmem);
break;
}
}
}
return docMembers;
}
///
/// Generates the code for parameters of the method
///
/// The form of the method contains names of all the members used
/// All of the members of an operation
/// A proper syntax for params of the simple method form
public string GetSimpleParameters(List form, IList operationMembers)
{
string currentParams = "";
foreach (var mem in form)
{
Member member = null;
foreach (var opmem in operationMembers)
{
if (opmem.PropertyName.Equals(mem, StringComparison.OrdinalIgnoreCase))
{
member = opmem;
break;
}
}
if (member == null)
throw new ArgumentOutOfRangeException("Unable to find matching member");
string type = member.DetermineType();
if (type == "Stream")
type = "System.IO.Stream";
if (currentParams == "")
currentParams = type + " " + GeneratorHelpers.CamelCaseParam(member.PropertyName);
else
currentParams = currentParams + ", " + type + " " + GeneratorHelpers.CamelCaseParam(member.PropertyName);
}
return currentParams;
}
}
#endregion
#region MethodForms
///
/// Represents the forms of a custom simple method
/// Forms of a simple method are the variety of parameters the method will have
///
public class MethodForms
{
public const string MethodFormsKey = "methodForms";
readonly JsonData _methodRoot;
readonly string _name;
public string Name { get { return _name; } }
///
/// Creates a representation of the forms for a custom simple method
///
/// The name of the method
/// The json model for the customization consisting of a list of forms
public MethodForms(string name, JsonData json)
{
this._name = name;
this._methodRoot = json;
}
///
/// The forms of a method which is a list of a list of member names for that form
///
public List> Forms
{
get
{
var forms = new List>();
var data = _methodRoot[MethodFormsKey];
if (data == null || !data.IsArray) return forms;
foreach (var formData in data)
{
var form = new List();
foreach (var item in (formData as JsonData))
{
form.Add(item.ToString());
}
forms.Add(form);
}
return forms;
}
}
}
#endregion
#endregion
#region CustomizationsModel
///
/// A model used to represent different types of customizations for generating a service
///
public class CustomizationsModel
{
public const string RetainOriginalMemberOrderingKey = "retainOriginalMemberOrdering";
public const string RuntimePipelineOverrideKey = "runtimePipelineOverride";
public const string OverridesKey = "overrides";
public const string OperationKey = "operation";
public const string TargetTypeKey = "targetType";
public const string NewTypeKey = "newType";
public const string ConstructorInputKey = "constructorInput";
public const string ConditionKey = "condition";
public const string NoArgOverloadsKey = "noArgOverloads";
public const string SuppressResultGenerationKey = "suppressResultGeneration";
public const string UseNullableTypeKey = "useNullableType";
public const string EmitIsSetPropertiesKey = "emitIsSetProperties";
public const string GlobalShapeKey = "*";
public const string ShapeSubstitutionsKey = "shapeSubstitutions";
public const string EmitAsShapeKey = "emitAsShape";
public const string RenameShapeKey = "renameShape";
public const string EmitFromMemberKey = "emitFromMember";
public const string ListMemberSuffixExclusionsKey = "listMemberSuffixExclusions";
public const string DataTypeSwapKey = "dataTypeSwap";
public const string TypeKey = "Type";
public const string MarshallerKey = "Marshaller";
public const string UnmarshallerKey = "Unmarshaller";
public const string SuppressSimpleMethodExceptionDocsKey = "suppressSimpleMethodExceptionDocs";
public const string XHttpMethodOverrideKey = "xHttpMethodOverride";
public const string DeprecationMessageKey = "message";
public const string ExamplesKey = "examples";
public const string GenerateUnmarshallerKey = "generateUnmarshaller";
public const string SkipUriPropertyValidationKey = "skipUriPropertyValidation";
public const string OverrideContentTypeKey = "overrideContentType";
JsonData _documentRoot;
SimpleMethodFormsModel _simpleMethodsModel;
SimpleConstructorsModel _simpleConstructorsModel;
///
/// A model that represents any custom methods to be generated by the client generator
///
public SimpleMethodFormsModel SimpleMethodsModel
{
get { return _simpleMethodsModel ?? (_simpleMethodsModel = new SimpleMethodFormsModel(_documentRoot)); }
}
///
/// A model that represents any simple constructors to be included while generating structures for the service
///
public SimpleConstructorsModel SimpleConstructorsModel
{
get
{
return _simpleConstructorsModel ??
(_simpleConstructorsModel = new SimpleConstructorsModel(_documentRoot));
}
}
///
/// Creates a customization model used to get customizations for a service
///
/// Reader to get the json text from
public CustomizationsModel(TextReader reader)
{
if (reader == null)
this._documentRoot = new JsonData();
else
Initialize(reader);
}
///
/// Creates a customization model used to get customizations for a service
///
/// Path to the file to read the customizations from
public CustomizationsModel(string modelPath)
{
if (string.IsNullOrEmpty(modelPath))
_documentRoot = new JsonData();
else
using (var reader = new StreamReader(modelPath))
Initialize(reader);
}
///
/// Sets the document root by parsing the json text
///
/// The reader to get json text from
void Initialize(TextReader reader)
{
this._documentRoot = JsonMapper.ToObject(reader);
}
RuntimePipelineOverride _runtimePipelineOverride;
///
/// Allows customizing pipeline overrides to be added to the client
///
public RuntimePipelineOverride PipelineOverride
{
get
{
if (_runtimePipelineOverride != null)
{
return _runtimePipelineOverride;
}
var data = _documentRoot[RuntimePipelineOverrideKey];
if (data != null)
{
_runtimePipelineOverride = new RuntimePipelineOverride();
foreach (var item in data[OverridesKey])
{
var jsonData = (JsonData) item;
_runtimePipelineOverride.Overrides.Add(
new RuntimePipelineOverride.Override()
{
OverrideMethod = jsonData[OperationKey] != null
? jsonData[OperationKey].ToString()
: null,
TargetType =
jsonData[TargetTypeKey] != null ? jsonData[TargetTypeKey].ToString() : null,
NewType = jsonData[NewTypeKey] != null ? jsonData[NewTypeKey].ToString() : null,
ConstructorInput = jsonData[ConstructorInputKey] != null ? jsonData[ConstructorInputKey].ToString() : "",
Condition = jsonData[ConditionKey] != null ? jsonData[ConditionKey].ToString() : ""
});
}
}
return _runtimePipelineOverride;
}
}
public bool SuppressSimpleMethodExceptionDocs
{
get
{
var flag = _documentRoot[SuppressSimpleMethodExceptionDocsKey];
if (flag != null && flag.IsBoolean)
{
return (bool)flag;
}
else if (flag != null && flag.IsString)
{
return bool.Parse((string)flag);
}
return false;
}
}
public bool RetainOriginalMemberOrdering
{
get
{
var flag = _documentRoot[RetainOriginalMemberOrderingKey];
if (flag != null && flag.IsBoolean)
{
return (bool)flag;
}
else if (flag != null && flag.IsString)
{
return bool.Parse((string)flag);
}
return false;
}
}
///
/// A list of uri properties for the service where we should not do validation for presence.
///
public List SkipUriPropertyValidations
{
get
{
var validations = new List();
var data = _documentRoot[SkipUriPropertyValidationKey];
if (data == null || !data.IsArray) return validations;
foreach (var item in data)
validations.Add(item.ToString());
return validations;
}
}
///
/// A list of methods that will be customized to have no arguments as one form of the operation
///
public List NoArgOverloads
{
get
{
var overloads = new List();
var data = _documentRoot[NoArgOverloadsKey];
if (data == null || !data.IsArray) return overloads;
foreach (var item in data)
overloads.Add(item.ToString());
overloads.Sort();
return overloads;
}
}
private HashSet _resultGenerationSuppressions = null;
///
/// A list of methods that will be customized to not have a derived
/// Result class due to an empty response.
///
public HashSet ResultGenerationSuppressions
{
get
{
if (_resultGenerationSuppressions == null)
{
_resultGenerationSuppressions = new HashSet();
var data = _documentRoot[SuppressResultGenerationKey];
if (data != null && data.IsArray)
{
foreach (var item in data)
_resultGenerationSuppressions.Add(item.ToString());
}
}
return _resultGenerationSuppressions;
}
}
public bool GenerateCustomUnmarshaller
{
get
{
var data = _documentRoot[GenerateUnmarshallerKey];
if (data == null)
return false;
return true;
}
}
///
/// Override for the Content-Type header
///
public string OverrideContentType
{
get
{
var data = _documentRoot[OverrideContentTypeKey];
if (data == null)
{
return null;
}
return (string)data;
}
}
public List CustomUnmarshaller
{
get
{
var data = _documentRoot[GenerateUnmarshallerKey];
if (data == null)
return null;
return (from JsonData s in data select s.ToString()).ToList();
}
}
///
/// Determines if the operation has a customization for creating a no argument method
///
/// The name of the operation to check for
/// True if the operation is in the list of noarg customizations
public bool CreateNoArgOverload(string operationName)
{
return NoArgOverloads.Contains(operationName, StringComparer.OrdinalIgnoreCase);
}
///
/// Gets the property modifier for a property in a shape (can be global) if so customized.
///
/// The name of the shape containing the property
/// The property name to look for
/// The property modifier, null if not found
public PropertyModifier GetPropertyModifier(string shapeName, string propertyName)
{
// for renames, check for a shape-specific rename first, then look to
// see if the property is globally renamed
if (ShapeModifiers.ContainsKey(shapeName))
{
var shapeModifiers = ShapeModifiers[shapeName];
if (shapeModifiers.IsModified(propertyName))
return shapeModifiers.PropertyModifier(propertyName);
}
if (ShapeModifiers.ContainsKey(GlobalShapeKey))
{
var globalShapeModifiers = ShapeModifiers[GlobalShapeKey];
if (globalShapeModifiers.IsModified(propertyName))
return globalShapeModifiers.PropertyModifier(propertyName);
}
return null;
}
///
/// Determines if the property has a customization to be set to nullable
///
/// The name of the shape the property is in
/// The name of the property
/// If the property is to be nullable
public bool UseNullable(string shapeName, string propertyName)
{
var data = _documentRoot[UseNullableTypeKey];
if (data == null)
return false;
var shape = data[shapeName] as JsonData;
if (shape == null || !shape.IsArray)
return false;
foreach (var name in shape)
{
if (string.Equals(name.ToString(), propertyName))
return true;
}
return false;
}
///
/// Determines if the collection property can be empty when being
/// sent to the service.
///
/// The name of the shape the property is in
/// The name of the property
/// If the collection property can be empty
public bool EmitIsSetProperties(string shapeName, string propertyName)
{
var data = _documentRoot[EmitIsSetPropertiesKey];
if (data == null)
return false;
var shape = data[shapeName] as JsonData;
if (shape == null || !shape.IsArray)
return false;
foreach (var name in shape)
{
if (string.Equals(name.ToString(), propertyName))
return true;
}
return false;
}
public string GetOverrideShapeName(string originalShapeName)
{
return GetRemappedShapeName(originalShapeName, RenameShapeKey);
}
///
/// Returns the name of the shape that should be used instead of the supplied shape
///
/// The shape to have its name changed
/// The new name, or null if not found
public string GetSubstituteShapeName(string originalShapeName)
{
return GetRemappedShapeName(originalShapeName, EmitAsShapeKey);
}
private string GetRemappedShapeName(string originalShapeName, string key)
{
var substitutionsData = _documentRoot[ShapeSubstitutionsKey];
if (substitutionsData == null)
return null;
var shapeRemapData = substitutionsData[originalShapeName] as JsonData;
if (null == shapeRemapData || null == shapeRemapData[key])
return null;
return (string)shapeRemapData[key];
}
///
/// Returns the substitution entry for a shape
///
///
///
public JsonData GetSubstituteShapeData(string originalShapeName)
{
var substitutionsData = _documentRoot[ShapeSubstitutionsKey];
if (substitutionsData == null)
return null;
return substitutionsData[originalShapeName];
}
///
/// Determines if the shape should be substituted with another
///
/// The original shape
/// If the shape is in the rename shapes
public bool IsSubstitutedShape(string originalShapeName)
{
var substitutionsData = _documentRoot[ShapeSubstitutionsKey];
if (substitutionsData == null)
return false;
var remapShape = substitutionsData[originalShapeName] as JsonData;
return remapShape != null;
}
///
/// Gets any customizations for a new data type to be used on a property
///
/// The name of the shape the property is defined within
/// The name of the property to check
/// The override datatype object, null if not found
public DataTypeOverride OverrideDataType(string shapeName, string propertyName)
{
var data = _documentRoot[DataTypeSwapKey];
if (data == null)
return null;
var shape = data[shapeName] as JsonData;
if (shape == null)
return null;
var jsonData = shape[propertyName];
if (jsonData == null)
return null;
var dataType = (string)jsonData[TypeKey];
string marshaller = null;
if (jsonData[MarshallerKey] != null && jsonData[MarshallerKey].IsString)
marshaller = (string)jsonData[MarshallerKey];
string unmarshaller = null;
if (jsonData[UnmarshallerKey] != null && jsonData[UnmarshallerKey].IsString)
unmarshaller = (string)jsonData[UnmarshallerKey];
return new DataTypeOverride(dataType, marshaller, unmarshaller);
}
#region ShapeModifier
///
/// The ShapeModifier allows shapes pulled from the model to be customized by excluding
/// properties, modifying them (rename etc) and injecting properties not in the original
/// model.
///
public class ShapeModifier
{
public const string ShapeModifiersKey = "shapeModifiers";
public const string ExcludeKey = "exclude";
public const string ModifyKey = "modify";
public const string InjectKey = "inject";
public const string CustomMarshallKey = "customMarshall";
public const string DeprecatedMessageKey = "deprecatedMessage";
public const string BackwardsCompatibleDateTimeKey = "backwardsCompatibleDateTimeProperties";
private readonly HashSet _excludedProperties;
private readonly HashSet _backwardsCompatibleDateTimeProperties;
private readonly Dictionary _modifiedProperties;
private readonly Dictionary _injectedProperties;
public string DeprecationMessage { get; private set; }
public ShapeModifier(JsonData data)
{
DeprecationMessage = data[DeprecatedMessageKey].CastToString();
_excludedProperties = ParseExclusions(data);
_backwardsCompatibleDateTimeProperties = ParseBackwardsCompatibleDateTimeProperties(data);
_modifiedProperties = ParseModifiers(data);
// Process additions after rename to allow for models where we
// add a 'convenience' member (for backwards compatibility) using
// the same name as an original (and now renamed) member.
_injectedProperties = ParseInjections(data);
}
#region Property Exclusion
// Exclusion modifier is a simple array of property names.
// "exclude": [ "propName1", "propName2" ]
private static HashSet ParseExclusions(JsonData data)
{
var exclusions = data[ShapeModifier.ExcludeKey]
?.Cast