xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/res/naming.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
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