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