1515package dev .cel .common ;
1616
1717import com .google .auto .value .AutoValue ;
18+ import com .google .common .base .Preconditions ;
1819import com .google .common .collect .ImmutableMap ;
1920import com .google .common .collect .ImmutableSet ;
2021import com .google .errorprone .annotations .CanIgnoreReturnValue ;
2122import com .google .errorprone .annotations .CheckReturnValue ;
2223import com .google .errorprone .annotations .Immutable ;
2324import java .util .LinkedHashMap ;
25+ import java .util .Locale ;
2426import java .util .Optional ;
2527
2628/** CelContainer holds a reference to an optional qualified container name and set of aliases. */
@@ -38,20 +40,125 @@ public abstract static class Builder {
3840
3941 private final LinkedHashMap <String , String > aliases = new LinkedHashMap <>();
4042
43+ abstract String name ();
44+
4145 /** Sets the fully-qualified name of the container. */
4246 public abstract Builder setName (String name );
4347
4448 abstract Builder setAliases (ImmutableMap <String , String > aliases );
4549
46- /** Alias associates a fully-qualified name with a user-defined alias. */
50+ /** See {@link #addAbbreviations(ImmutableSet)} for documentation. */
51+ @ CanIgnoreReturnValue
52+ public Builder addAbbreviations (String ... qualifiedNames ) {
53+ Preconditions .checkNotNull (qualifiedNames );
54+ return addAbbreviations (ImmutableSet .copyOf (qualifiedNames ));
55+ }
56+
57+ /**
58+ * Configures a set of simple names as abbreviations for fully-qualified names.
59+ *
60+ * <p>An abbreviation is a simple name that expands to a fully-qualified name. Abbreviations can
61+ * be useful when working with variables, functions, and especially types from multiple
62+ * namespaces:
63+ *
64+ * <pre>{@code
65+ * // CEL object construction
66+ * qual.pkg.version.ObjTypeName{
67+ * field: alt.container.ver.FieldTypeName{value: ...}
68+ * }
69+ * }</pre>
70+ *
71+ * <p>Only one the qualified names above may be used as the CEL container, so at least one of
72+ * these references must be a long qualified name within an otherwise short CEL program. Using
73+ * the following abbreviations, the program becomes much simpler:
74+ *
75+ * <pre>{@code
76+ * // CEL Java option
77+ * CelContainer.newBuilder().addAbbreviations("qual.pkg.version.ObjTypeName", "alt.container.ver.FieldTypeName").build()
78+ * }
79+ * {@code
80+ * // Simplified Object construction
81+ * ObjTypeName{field: FieldTypeName{value: ...}}
82+ * }</pre>
83+ *
84+ * <p>There are a few rules for the qualified names and the simple abbreviations generated from
85+ * them:
86+ *
87+ * <ul>
88+ * <li>Qualified names must be dot-delimited, e.g. `package.subpkg.name`.
89+ * <li>The last element in the qualified name is the abbreviation.
90+ * <li>Abbreviations must not collide with each other.
91+ * <li>The abbreviation must not collide with unqualified names in use.
92+ * </ul>
93+ *
94+ * <p>Abbreviations are distinct from container-based references in the following important
95+ * ways:
96+ *
97+ * <ul>
98+ * <li>Abbreviations must expand to a fully-qualified name.
99+ * <li>Expanded abbreviations do not participate in namespace resolution.
100+ * <li>Abbreviation expansion is done instead of the container search for a matching
101+ * identifier.
102+ * <li>Containers follow C++ namespace resolution rules with searches from the most qualified
103+ * name to the least qualified name.
104+ * <li>Container references within the CEL program may be relative, and are resolved to fully
105+ * qualified names at either type-check time or program plan time, whichever comes first.
106+ * </ul>
107+ *
108+ * <p>If there is ever a case where an identifier could be in both the container and as an
109+ * abbreviation, the abbreviation wins as this will ensure that the meaning of a program is
110+ * preserved between compilations even as the container evolves.
111+ */
112+ @ CanIgnoreReturnValue
113+ public Builder addAbbreviations (ImmutableSet <String > qualifiedNames ) {
114+ for (String qualifiedName : qualifiedNames ) {
115+ qualifiedName = qualifiedName .trim ();
116+ for (int i = 0 ; i < qualifiedName .length (); i ++) {
117+ if (!isIdentifierChar (qualifiedName .charAt (i ))) {
118+ throw new IllegalArgumentException (
119+ String .format (
120+ "invalid qualified name: %s, wanted name of the form 'qualified.name'" ,
121+ qualifiedName ));
122+ }
123+ }
124+
125+ int index = qualifiedName .lastIndexOf ("." );
126+ if (index <= 0 || index >= qualifiedName .length () - 1 ) {
127+ throw new IllegalArgumentException (
128+ String .format (
129+ "invalid qualified name: %s, wanted name of the form 'qualified.name'" ,
130+ qualifiedName ));
131+ }
132+
133+ String alias = qualifiedName .substring (index + 1 );
134+ aliasAs (AliasKind .ABBREVIATION , qualifiedName , alias );
135+ }
136+
137+ return this ;
138+ }
139+
140+ /**
141+ * Alias associates a fully-qualified name with a user-defined alias.
142+ *
143+ * <p>In general, {@link #addAbbreviations} is preferred to aliasing since the names generated
144+ * from the Abbrevs option are more easily traced back to source code. Aliasing is useful for
145+ * propagating alias configuration from one container instance to another, and may also be
146+ * useful for remapping poorly chosen protobuf message / package names.
147+ *
148+ * <p>Note: all the rules that apply to abbreviations also apply to aliasing.
149+ */
47150 @ CanIgnoreReturnValue
48151 public Builder addAlias (String alias , String qualifiedName ) {
49- validateAliasOrThrow ("alias" , qualifiedName , alias );
50- aliases .put (alias , qualifiedName );
152+ aliasAs (AliasKind .ALIAS , qualifiedName , alias );
51153 return this ;
52154 }
53155
54- private void validateAliasOrThrow (String kind , String qualifiedName , String alias ) {
156+ private void aliasAs (AliasKind kind , String qualifiedName , String alias ) {
157+ validateAliasOrThrow (kind , qualifiedName , alias );
158+ aliases .put (alias , qualifiedName );
159+ }
160+
161+ private void validateAliasOrThrow (AliasKind kind , String qualifiedName , String alias ) {
55162 if (alias .isEmpty () || alias .contains ("." )) {
56163 throw new IllegalArgumentException (
57164 String .format (
@@ -76,6 +183,14 @@ private void validateAliasOrThrow(String kind, String qualifiedName, String alia
76183 "%s collides with existing reference: name=%s, %s=%s, existing=%s" ,
77184 kind , qualifiedName , kind , alias , aliasRef ));
78185 }
186+
187+ String containerName = name ();
188+ if (containerName .startsWith (alias + "." ) || containerName .equals (alias )) {
189+ throw new IllegalArgumentException (
190+ String .format (
191+ "%s collides with container name: name=%s, %s=%s, container=%s" ,
192+ kind , qualifiedName , kind , alias , containerName ));
193+ }
79194 }
80195
81196 abstract CelContainer autoBuild ();
@@ -135,6 +250,14 @@ public ImmutableSet<String> resolveCandidateNames(String typeName) {
135250 return candidates .add (typeName ).build ();
136251 }
137252
253+ public static Builder newBuilder () {
254+ return new AutoValue_CelContainer .Builder ().setName ("" );
255+ }
256+
257+ public static CelContainer ofName (String containerName ) {
258+ return newBuilder ().setName (containerName ).build ();
259+ }
260+
138261 private Optional <String > findAlias (String name ) {
139262 // If an alias exists for the name, ensure it is searched last.
140263 String simple = name ;
@@ -152,11 +275,22 @@ private Optional<String> findAlias(String name) {
152275 return Optional .of (alias + qualifier );
153276 }
154277
155- public static Builder newBuilder () {
156- return new AutoValue_CelContainer .Builder ().setName ("" );
278+ private static boolean isIdentifierChar (int r ) {
279+ if (r > 127 ) {
280+ // Not ASCII
281+ return false ;
282+ }
283+
284+ return r == '.' || r == '_' || Character .isLetter (r ) || Character .isDigit (r );
157285 }
158286
159- public static CelContainer ofName (String containerName ) {
160- return newBuilder ().setName (containerName ).build ();
287+ private enum AliasKind {
288+ ALIAS ,
289+ ABBREVIATION ;
290+
291+ @ Override
292+ public String toString () {
293+ return this .name ().toLowerCase (Locale .getDefault ());
294+ }
161295 }
162296}
0 commit comments