From 8a7ec8c3a9ea66c935267f34735e262bf18abe77 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 9 Feb 2019 13:22:20 +0100 Subject: [PATCH] Properly handle natural language map. - Deserialization now happens correctly into the rdf:langString property. - Kluges to make sure the member is being set and referred to in the generation code. - No longer generate special member for natural language map nor a special Is method. Use the rdf:langString generated proeprties. - Keep the Set/Get special language members for handling individual languages. --- astool/convert/convert.go | 10 +++- astool/gen/funcprop.go | 112 ++++++++++++++++++++++++-------------- astool/gen/nonfuncprop.go | 78 +++++++++++++++++++++++--- astool/gen/property.go | 30 ++++------ astool/gen/type.go | 8 +++ astool/rdf/ontology.go | 3 +- 6 files changed, 169 insertions(+), 72 deletions(-) diff --git a/astool/convert/convert.go b/astool/convert/convert.go index 101b800..bcfeea4 100644 --- a/astool/convert/convert.go +++ b/astool/convert/convert.go @@ -670,7 +670,7 @@ func (c *Converter) convertFunctionalProperty(p rdf.VocabularyProperty, if len(examples) > 0 { comment = fmt.Sprintf("%s\n\n%s", comment, strings.Join(examples, "\n\n")) } - fp = gen.NewFunctionalPropertyGenerator( + fp, e = gen.NewFunctionalPropertyGenerator( v.GetName(), v.URI, vocabNameToAlias(v.GetName()), @@ -679,6 +679,9 @@ func (c *Converter) convertFunctionalProperty(p rdf.VocabularyProperty, comment, k, p.NaturalLanguageMap) + if e != nil { + return + } e = backPopulateProperty(v.Registry, p, genRefs, fp) if e != nil { return @@ -712,7 +715,7 @@ func (c *Converter) convertNonFunctionalProperty(p rdf.VocabularyProperty, if len(examples) > 0 { comment = fmt.Sprintf("%s\n\n%s", comment, strings.Join(examples, "\n\n")) } - nfp = gen.NewNonFunctionalPropertyGenerator( + nfp, e = gen.NewNonFunctionalPropertyGenerator( v.GetName(), v.URI, vocabNameToAlias(v.GetName()), @@ -721,6 +724,9 @@ func (c *Converter) convertNonFunctionalProperty(p rdf.VocabularyProperty, comment, k, p.NaturalLanguageMap) + if e != nil { + return + } e = backPopulateProperty(v.Registry, p, genRefs, nfp) if e != nil { return diff --git a/astool/gen/funcprop.go b/astool/gen/funcprop.go index 74d66bf..937d7b1 100644 --- a/astool/gen/funcprop.go +++ b/astool/gen/funcprop.go @@ -34,7 +34,20 @@ func NewFunctionalPropertyGenerator(vocabName string, name Identifier, comment string, kinds []Kind, - hasNaturalLanguageMap bool) *FunctionalPropertyGenerator { + hasNaturalLanguageMap bool) (*FunctionalPropertyGenerator, error) { + // Ensure that the natural language map has the langString kind. + if hasNaturalLanguageMap { + found := false + for _, k := range kinds { + if k.Name.LowerName == "langString" { + found = true + break + } + } + if !found { + return nil, fmt.Errorf("Property has natural language map, but not an rdf:langString kind") + } + } return &FunctionalPropertyGenerator{ PropertyGenerator: PropertyGenerator{ vocabName: vocabName, @@ -46,7 +59,7 @@ func NewFunctionalPropertyGenerator(vocabName string, comment: comment, kinds: kinds, }, - } + }, nil } // InterfaceDefinition creates an interface definition in the provided package. @@ -156,25 +169,6 @@ func (p *FunctionalPropertyGenerator) funcs() []*codegen.Method { ), } if p.hasNaturalLanguageMap { - // IsLanguageMap Method - methods = append(methods, - codegen.NewCommentedValueMethod( - p.GetPrivatePackage().Path(), - isLanguageMapMethod, - p.StructName(), - /*params=*/ nil, - []jen.Code{jen.Bool()}, - []jen.Code{ - jen.Return(jen.Id(codegen.This()).Dot(langMapMember).Op("!=").Nil()), - }, - fmt.Sprintf( - "%s determines if this property is represented by a natural language map. When true, use %s, %s, and %s methods to access and mutate the natural language map. The %s method can be used to clear the natural language map. Note that this method is only used for natural language representations, and does not determine the presence nor absence of other values for this property.", - isLanguageMapMethod, - hasLanguageMethod, - getLanguageMethod, - setLanguageMethod, - p.clearMethodName(), - ))) // HasLanguage Method methods = append(methods, codegen.NewCommentedValueMethod( @@ -366,6 +360,20 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co typeDeserializeFns = typeDeserializeFns.Add(tmp) } } + mapProperty := jen.Empty() + if p.hasNaturalLanguageMap { + mapProperty = jen.If( + jen.Id("!ok"), + ).Block( + jen.Commentf("Attempt to find the map instead."), + jen.List( + jen.Id("i"), + jen.Id("ok"), + ).Op("=").Id("m").Index( + jen.Id("propName").Op("+").Lit("Map"), + ), + ) + } var deserialize *codegen.Function if p.asIterator { deserialize = codegen.NewCommentedFunction( @@ -384,13 +392,7 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co ).Block( jen.Id("alias").Op("=").Id("a"), ), - p.wrapDeserializeCode(valueDeserializeFns, typeDeserializeFns).Line().Return( - jen.Nil(), - jen.Qual("fmt", "Errorf").Call( - jen.Lit("could not deserialize %q property"), - jen.Lit(p.PropertyName()), - ), - ), + p.wrapDeserializeCode(valueDeserializeFns, typeDeserializeFns), }, fmt.Sprintf("%s creates an iterator from an element that has been unmarshalled from a text or binary format.", p.DeserializeFnName())) } else { @@ -421,15 +423,14 @@ func (p *FunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, *co jen.Lit(p.PropertyName()), ), ), - jen.If( - jen.List( - jen.Id("i"), - jen.Id("ok"), - ).Op(":=").Id("m").Index( - jen.Id("propName"), - ), + jen.List( + jen.Id("i"), jen.Id("ok"), - ).Block( + ).Op(":=").Id("m").Index( + jen.Id("propName"), + ), + mapProperty, + jen.If(jen.Id("ok")).Block( p.wrapDeserializeCode(valueDeserializeFns, typeDeserializeFns), ), jen.Return( @@ -471,9 +472,6 @@ func (p *FunctionalPropertyGenerator) singleTypeDef() *codegen.Struct { } // TODO: Normalize alias of values when setting on this property. kindMembers = append(kindMembers, jen.Id(aliasMember).String()) - if p.hasNaturalLanguageMap { - kindMembers = append(kindMembers, jen.Id(langMapMember).Map(jen.String()).String()) - } if p.asIterator { kindMembers = append(kindMembers, jen.Id(myIndexMemberName).Int()) kindMembers = append(kindMembers, jen.Id(parentMemberName).Qual(p.GetPublicPackage().Path(), p.parentTypeInterfaceName())) @@ -487,6 +485,7 @@ func (p *FunctionalPropertyGenerator) singleTypeDef() *codegen.Struct { methods = append(methods, p.singleTypeFuncs()...) methods = append(methods, p.funcs()...) methods = append(methods, p.commonMethods()...) + methods = append(methods, p.nameMethod()) return codegen.NewStruct(comment, p.StructName(), methods, @@ -719,9 +718,6 @@ func (p *FunctionalPropertyGenerator) multiTypeDef() *codegen.Struct { kindMembers = append(kindMembers, p.iriMemberDef()) } kindMembers = append(kindMembers, jen.Id(aliasMember).String()) - if p.hasNaturalLanguageMap { - kindMembers = append(kindMembers, jen.Id(langMapMember).Map(jen.String()).String()) - } explanation := "At most, one type of value can be present, or none at all. Setting a value will clear the other types of values so that only one of the 'Is' methods will return true. It is possible to clear all values, so that this property is empty." comment := fmt.Sprintf( "%s is the functional property %q. It is permitted to be one of multiple value types. %s", @@ -747,6 +743,7 @@ func (p *FunctionalPropertyGenerator) multiTypeDef() *codegen.Struct { methods = append(methods, p.multiTypeFuncs()...) methods = append(methods, p.funcs()...) methods = append(methods, p.commonMethods()...) + methods = append(methods, p.nameMethod()) return codegen.NewStruct(comment, p.StructName(), methods, @@ -1150,3 +1147,34 @@ func (p *FunctionalPropertyGenerator) hasValueKind() bool { } return false } + +// nameMethod returns the Name method for this functional property. +func (p *FunctionalPropertyGenerator) nameMethod() *codegen.Method{ + nameImpl := jen.Return( + jen.Lit(p.PropertyName()), + ) + if p.hasNaturalLanguageMap { + nameImpl = jen.If( + jen.Id(codegen.This()).Dot(isLanguageMapMethod).Call(), + ).Block( + jen.Return( + jen.Lit(p.PropertyName() + "Map"), + ), + ).Else().Block( + jen.Return( + jen.Lit(p.PropertyName()), + ), + ) + } + return codegen.NewCommentedValueMethod( + p.GetPrivatePackage().Path(), + nameMethod, + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.String()}, + []jen.Code{ + nameImpl, + }, + fmt.Sprintf("%s returns the name of this property: %q.", nameMethod, p.PropertyName()), + ) +} diff --git a/astool/gen/nonfuncprop.go b/astool/gen/nonfuncprop.go index 4a5207f..7522c6f 100644 --- a/astool/gen/nonfuncprop.go +++ b/astool/gen/nonfuncprop.go @@ -35,7 +35,20 @@ func NewNonFunctionalPropertyGenerator(vocabName string, name Identifier, comment string, kinds []Kind, - hasNaturalLanguageMap bool) *NonFunctionalPropertyGenerator { + hasNaturalLanguageMap bool) (*NonFunctionalPropertyGenerator, error) { + // Ensure that the natural language map has the langString kind. + if hasNaturalLanguageMap { + found := false + for _, k := range kinds { + if k.Name.LowerName == "langString" { + found = true + break + } + } + if !found { + return nil, fmt.Errorf("Property has natural language map, but not an rdf:langString kind") + } + } return &NonFunctionalPropertyGenerator{ PropertyGenerator: PropertyGenerator{ vocabName: vocabName, @@ -47,7 +60,7 @@ func NewNonFunctionalPropertyGenerator(vocabName string, comment: comment, kinds: kinds, }, - } + }, nil } // InterfaceDefinitions creates interface definitions in the provided package. @@ -539,6 +552,7 @@ func (p *NonFunctionalPropertyGenerator) funcs() []*codegen.Method { }, fmt.Sprintf("%s returns the JSONLD URIs required in the context string for this property and the specific values that are set. The value in the map is the alias used to import the property's value or values.", contextMethod))) methods = append(methods, p.commonMethods()...) + methods = append(methods, p.nameMethod()) return methods } @@ -621,6 +635,20 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, ), ) } + mapProperty := jen.Empty() + if p.hasNaturalLanguageMap { + mapProperty = jen.If( + jen.Id("!ok"), + ).Block( + jen.Commentf("Attempt to find the map instead."), + jen.List( + jen.Id("i"), + jen.Id("ok"), + ).Op("=").Id("m").Index( + jen.Id("propName").Op("+").Lit("Map"), + ), + ) + } deserialize := codegen.NewCommentedFunction( p.GetPrivatePackage().Path(), p.DeserializeFnName(), @@ -647,13 +675,14 @@ func (p *NonFunctionalPropertyGenerator) serializationFuncs() (*codegen.Method, jen.Lit(p.PropertyName()), ), ), + jen.List( + jen.Id("i"), + jen.Id("ok"), + ).Op(":=").Id("m").Index( + jen.Id("propName"), + ), + mapProperty, jen.If( - jen.List( - jen.Id("i"), - jen.Id("ok"), - ).Op(":=").Id("m").Index( - jen.Id("propName"), - ), jen.Id("ok"), ).Block( jen.Id(codegen.This()).Op(":=").Op("&").Id(p.StructName()).Values( @@ -717,3 +746,36 @@ func (p *NonFunctionalPropertyGenerator) thisIRI() *jen.Statement { } return nil } + +// nameMethod returns the Name method for this non-functional property. +func (p *NonFunctionalPropertyGenerator) nameMethod() *codegen.Method{ + nameImpl := jen.Return( + jen.Lit(p.PropertyName()), + ) + if p.hasNaturalLanguageMap { + nameImpl = jen.If( + jen.Id(codegen.This()).Dot(lenMethod).Call().Op("==").Lit(1).Op( + "&&", + ).Id(codegen.This()).Dot(atMethodName).Call(jen.Lit(0)).Dot(isLanguageMapMethod).Call(), + ).Block( + jen.Return( + jen.Lit(p.PropertyName() + "Map"), + ), + ).Else().Block( + jen.Return( + jen.Lit(p.PropertyName()), + ), + ) + } + return codegen.NewCommentedValueMethod( + p.GetPrivatePackage().Path(), + nameMethod, + p.StructName(), + /*params=*/ nil, + []jen.Code{jen.String()}, + []jen.Code{ + nameImpl, + }, + fmt.Sprintf("%s returns the name of this property: %q.", nameMethod, p.PropertyName()), + ) +} diff --git a/astool/gen/property.go b/astool/gen/property.go index 4c15149..9a35a1a 100644 --- a/astool/gen/property.go +++ b/astool/gen/property.go @@ -32,7 +32,6 @@ const ( nameMethod = "Name" serializeIteratorMethod = "serialize" deserializeIteratorMethod = "deserialize" - isLanguageMapMethod = "IsLanguageMap" hasLanguageMethod = "HasLanguage" getLanguageMethod = "GetLanguage" setLanguageMethod = "SetLanguage" @@ -45,7 +44,10 @@ const ( contextMethod = "JSONLDContext" // Member names for generated code unknownMemberName = "unknown" - langMapMember = "langMap" + // Reference to the rdf:langString member! Kludge: both of these must be + // kept in sync with the generated code. + langMapMember = "langStringMember" + isLanguageMapMethod = "IsLangString" // Kind Index constants iriKindIndex = -2 noneOrUnknownKindIndex = -1 @@ -202,6 +204,12 @@ type PropertyGenerator struct { asIterator bool } +// HasNaturalLanguageMap returns whether this property has a natural language +// map. +func (p *PropertyGenerator) HasNaturalLanguageMap() bool { + return p.hasNaturalLanguageMap +} + // VocabName returns this property's vocabulary name. func (p *PropertyGenerator) VocabName() string { return p.vocabName @@ -369,23 +377,7 @@ func (p *PropertyGenerator) clearMethodName() string { } // commonMethods returns methods common to every property. -func (p *PropertyGenerator) commonMethods() []*codegen.Method { - // Name method - m := []*codegen.Method{ - codegen.NewCommentedValueMethod( - p.GetPrivatePackage().Path(), - nameMethod, - p.StructName(), - /*params=*/ nil, - []jen.Code{jen.String()}, - []jen.Code{ - jen.Return( - jen.Lit(p.PropertyName()), - ), - }, - fmt.Sprintf("%s returns the name of this property: %q.", nameMethod, p.PropertyName()), - ), - } +func (p *PropertyGenerator) commonMethods() (m []*codegen.Method) { if p.asIterator { // Next & Prev methods m = append(m, codegen.NewCommentedValueMethod( diff --git a/astool/gen/type.go b/astool/gen/type.go index 526cde2..898cf63 100644 --- a/astool/gen/type.go +++ b/astool/gen/type.go @@ -82,6 +82,7 @@ type Property interface { InterfaceName() string SetKindFns(docName, idName string, kind *jen.Statement, deser *codegen.Method) error DeserializeFnName() string + HasNaturalLanguageMap() bool } // TypeGenerator represents an ActivityStream type definition to generate in Go. @@ -780,6 +781,13 @@ func (t *TypeGenerator) deserializationFn() (deser *codegen.Function) { ).Block( jen.Continue(), ) + if prop.HasNaturalLanguageMap() { + knownProps = knownProps.Else().If( + jen.Id("k").Op("==").Lit(prop.PropertyName() + "Map"), + ).Block( + jen.Continue(), + ) + } } knownProps = knownProps.Commentf("End: Code that ensures a property name is unknown").Line() unknownCode := jen.Commentf("Begin: Unknown deserialization").Line().For( diff --git a/astool/rdf/ontology.go b/astool/rdf/ontology.go index 8bfef89..602c64f 100644 --- a/astool/rdf/ontology.go +++ b/astool/rdf/ontology.go @@ -158,10 +158,11 @@ func (l *langstring) Exit(key string, ctx *ParsingContext) (bool, error) { // Apply sets the langstring value in the context as a referenced spec. func (l *langstring) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) { for k, p := range ctx.Result.Vocab.Properties { - for _, ref := range p.Range { + for i, ref := range p.Range { if ref.Name == langstringSpec && ref.Vocab == l.alias { p.NaturalLanguageMap = true ctx.Result.Vocab.Properties[k] = p + p.Range = append(p.Range[:i], p.Range[i+1:]...) break } }