1// Copyright 2018 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package res 16 17import ( 18 "fmt" 19 "strings" 20 21 rdpb "src/tools/ak/res/proto/res_data_go_proto" 22 rmpb "src/tools/ak/res/proto/res_meta_go_proto" 23 "google.golang.org/protobuf/proto" 24) 25 26// FullyQualifiedName represents the components of a name. 27type FullyQualifiedName struct { 28 Package string 29 Type Type 30 Name string 31} 32 33// ValuesResource represents a resource element. 34type ValuesResource struct { 35 Src *PathInfo 36 N FullyQualifiedName 37 Payload []byte 38} 39 40// SetResource sets all the name related fields on the top level resource proto. 41func (f FullyQualifiedName) SetResource(r *rdpb.Resource) error { 42 rt, err := f.Type.Enum() 43 if err != nil { 44 return err 45 } 46 r.ResourceType = rt 47 r.Name = protoNameSanitizer.Replace(f.Name) 48 return nil 49} 50 51// SetMetaData sets all name related fields for this style on a StyleableMetaData proto 52func (f FullyQualifiedName) SetMetaData(md *rmpb.StyleableMetaData) error { 53 if f.Type != Styleable { 54 return ErrWrongType 55 } 56 md.Name = proto.String(protoNameSanitizer.Replace(f.Name)) 57 return nil 58} 59 60var ( 61 protoNameSanitizer = strings.NewReplacer(".", "_") 62 javaNameSanitizer = strings.NewReplacer(":", "_", ".", "_") 63) 64 65// JavaName returns a version of the FullyQualifiedName that should be used for resource identifier fields. 66func (f FullyQualifiedName) JavaName() (string, error) { 67 if !f.Type.IsReal() { 68 return "", ErrWrongType 69 } 70 return javaNameSanitizer.Replace(f.Name), nil 71} 72 73// StyleableAttrName creates the java identifier for referencing this attribute in the given 74// style. 75func StyleableAttrName(styleable, attr FullyQualifiedName) (string, error) { 76 if styleable.Type != Styleable || attr.Type != Attr { 77 return "", ErrWrongType 78 } 79 js, err := styleable.JavaName() 80 if err != nil { 81 return "", err 82 } 83 ja, err := attr.JavaName() 84 if err != nil { 85 return "", err 86 } 87 88 if attr.Package == "android" { 89 return fmt.Sprintf("%s_android_%s", js, ja), nil 90 } 91 return fmt.Sprintf("%s_%s", js, ja), nil 92} 93 94// ParseName is given a name string and optional context about the name (what type the name may be) 95// and attempts to extract the local name, Type, and package from the unparsed input. The format of 96// unparsed names is flexible and not well specified. 97// A FullyQualifiedName's String method will emit pkg:type/name which every tool understands, but 98// ParseName will encounter input like ?type:pkg/name - an undocumented, but legal way to specify a 99// reference to a style. If unparsed is so mangled that a legal name cannot possibly be determined, 100// it will return an error. 101func ParseName(unparsed string, resType Type) (FullyQualifiedName, error) { 102 fqn := removeRef(unparsed) 103 fqn.Type = resType 104 pkgIdx := strings.Index(fqn.Name, ":") 105 typeIdx := strings.Index(fqn.Name, "/") 106 if pkgIdx == 0 || typeIdx == 0 { 107 return FullyQualifiedName{}, fmt.Errorf("malformed name %q - can not start with ':' or '/'", unparsed) 108 } 109 110 if typeIdx != -1 { 111 if pkgIdx != -1 { 112 if pkgIdx < typeIdx { 113 // Package, type and name (pkg:type/name) 114 t, err := ParseType(fqn.Name[pkgIdx+1 : typeIdx]) 115 if err != nil { 116 // the name has illegal type in it that we'll never be able to scrub out. 117 return FullyQualifiedName{}, err 118 } 119 fqn.Type = t 120 fqn.Package = fqn.Name[:pkgIdx] 121 fqn.Name = fqn.Name[typeIdx+1:] 122 123 } else { 124 // Package, type and name, type and package swapped (type:pkg/name) 125 t, err := ParseType(fqn.Name[:typeIdx]) 126 if err != nil { 127 // the name has illegal type in it that we'll never be able to scrub out. 128 return FullyQualifiedName{}, err 129 } 130 fqn.Type = t 131 fqn.Package = fqn.Name[typeIdx+1 : pkgIdx] 132 fqn.Name = fqn.Name[pkgIdx+1:] 133 } 134 } else { 135 // Only type and name (type/name) 136 t, err := ParseType(fqn.Name[:typeIdx]) 137 if err != nil { 138 // the name has illegal type in it that we'll never be able to scrub out. 139 return FullyQualifiedName{}, err 140 } 141 fqn.Type = t 142 fqn.Name = fqn.Name[typeIdx+1:] 143 } 144 } else { 145 // Only package and name (pkg:name) 146 if pkgIdx != -1 { 147 fqn.Package = fqn.Name[:pkgIdx] 148 fqn.Name = fqn.Name[pkgIdx+1:] 149 } 150 } 151 152 if fqn.Package == "" { 153 fqn.Package = "res-auto" 154 } 155 156 if fqn.Type == UnknownType { 157 return FullyQualifiedName{}, fmt.Errorf("cannot determine type from %q and %v - not a valid name", unparsed, resType) 158 } 159 if fqn.Name == "" { 160 return FullyQualifiedName{}, fmt.Errorf("cannot determine name from %q and %v - not a valid name", unparsed, resType) 161 } 162 return fqn, nil 163} 164 165func removeRef(unparsed string) (fqn FullyQualifiedName) { 166 fqn.Name = unparsed 167 if len(fqn.Name) > 2 && (strings.HasPrefix(fqn.Name, "@") || strings.HasPrefix(fqn.Name, "?")) { 168 fqn.Name = fqn.Name[1:] 169 } 170 return 171} 172 173func (f FullyQualifiedName) String() string { 174 return fmt.Sprintf("%s:%s/%s", f.Package, f.Type, f.Name) 175} 176