Skip to content

Commit 68dd475

Browse files
committed
Add and remove members to index automatically
This commit updates the ShapeIndex to automatically add and remove members from shapes that have members. This required some extra functionality be introduced to shapes which then in turn resulted in cleanup in other places in the codebase. 1. ShapeIndex now automatically adds and removes members when shapes are added. 2. There is now a members() method on all shapes to return the members contained in the shape (if any). Shapes that have no members just return an empty collection by default, and shapes that do contain members override the abstract method. 3. Because members are now less important to contain a reference to outside of their containing shapes (since you don't need to explicitly add them to a shape index), you can now automatically create member shapes for container shapes by just passing in the target of the member or passing both the target and a consumer that accepts the created MemberShape.Builder type. This removes the need to create the right shape ID for the shape (suffixing with "$" + the member name). 4. ShapeIndex doesn't really need a ConcurrentHashMap to cache different groups of shape types. The code that was used for that was overly complicated and likely made it slower rather than faster (the majority of models will use the prelude and will have more than 50 shapes, so the optimizations that were in place don't really make sense any more). 5. Because there's so much shared code between StructureShape and UnionShape, I created an abstract class for their shape and builders to make them simpler to maintain. 6. A couple model transformations were updated to use the new members() method. 7. Members of structures and unions are now sorted rather than maintain insert order. Nothing should ever depend on a stable order based on inserts since Smithy models can be loaded and aggregated from multiple sources.
1 parent f302ec5 commit 68dd475

23 files changed

+589
-443
lines changed

smithy-model/src/main/java/software/amazon/smithy/model/shapes/CollectionShape.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515

1616
package software.amazon.smithy.model.shapes;
1717

18+
import java.util.Collection;
19+
import java.util.Collections;
1820
import java.util.Objects;
21+
import java.util.function.Consumer;
1922
import software.amazon.smithy.utils.SmithyBuilder;
2023

2124
/**
@@ -45,6 +48,11 @@ public final MemberShape getMember() {
4548
return member;
4649
}
4750

51+
@Override
52+
public Collection<MemberShape> members() {
53+
return Collections.singletonList(member);
54+
}
55+
4856
@Override
4957
public boolean equals(Object other) {
5058
return super.equals(other) && getMember().equals(((CollectionShape) other).getMember());
@@ -71,6 +79,38 @@ public B member(MemberShape member) {
7179
return (B) this;
7280
}
7381

82+
/**
83+
* Sets the member of the collection.
84+
* @param target Target of the member.
85+
* @return Returns the builder.
86+
*/
87+
public B member(ShapeId target) {
88+
return member(target, null);
89+
}
90+
91+
/**
92+
* Sets the member of the collection.
93+
*
94+
* @param target Target of the member.
95+
* @param memberUpdater Consumer that can update the created member shape.
96+
* @return Returns the builder.
97+
*/
98+
public B member(ShapeId target, Consumer<MemberShape.Builder> memberUpdater) {
99+
if (getId() == null) {
100+
throw new IllegalStateException("An id must be set before setting a member with a target");
101+
}
102+
103+
MemberShape.Builder builder = MemberShape.builder()
104+
.target(target)
105+
.id(getId().withMember("member"));
106+
107+
if (memberUpdater != null) {
108+
memberUpdater.accept(builder);
109+
}
110+
111+
return member(builder.build());
112+
}
113+
74114
@Override
75115
public final B addMember(MemberShape member) {
76116
return member(member);

smithy-model/src/main/java/software/amazon/smithy/model/shapes/MapShape.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515

1616
package software.amazon.smithy.model.shapes;
1717

18+
import java.util.Collection;
1819
import java.util.Objects;
1920
import java.util.Optional;
21+
import java.util.function.Consumer;
22+
import software.amazon.smithy.utils.ListUtils;
2023
import software.amazon.smithy.utils.SmithyBuilder;
2124
import software.amazon.smithy.utils.ToSmithyBuilder;
2225

@@ -85,6 +88,11 @@ public MemberShape getKey() {
8588
return key;
8689
}
8790

91+
@Override
92+
public Collection<MemberShape> members() {
93+
return ListUtils.of(key, value);
94+
}
95+
8896
@Override
8997
public boolean equals(Object other) {
9098
if (!super.equals(other)) {
@@ -135,5 +143,71 @@ public Builder addMember(MemberShape member) {
135143
throw new IllegalStateException("Invalid member given to MapShape builder: " + member.getId());
136144
}
137145
}
146+
147+
/**
148+
* Sets the key member of the map.
149+
*
150+
* @param target Target of the member.
151+
* @return Returns the builder.
152+
*/
153+
public Builder key(ShapeId target) {
154+
return key(target, null);
155+
}
156+
157+
/**
158+
* Sets the key member of the map.
159+
*
160+
* @param target Target of the member.
161+
* @param memberUpdater Consumer that can update the created member shape.
162+
* @return Returns the builder.
163+
*/
164+
public Builder key(ShapeId target, Consumer<MemberShape.Builder> memberUpdater) {
165+
if (getId() == null) {
166+
throw new IllegalStateException("An id must be set before setting a member with a target");
167+
}
168+
169+
MemberShape.Builder builder = MemberShape.builder()
170+
.target(target)
171+
.id(getId().withMember("key"));
172+
173+
if (memberUpdater != null) {
174+
memberUpdater.accept(builder);
175+
}
176+
177+
return key(builder.build());
178+
}
179+
180+
/**
181+
* Sets the value member of the map.
182+
*
183+
* @param target Target of the member.
184+
* @return Returns the builder.
185+
*/
186+
public Builder value(ShapeId target) {
187+
return value(target, null);
188+
}
189+
190+
/**
191+
* Sets the value member of the map.
192+
*
193+
* @param target Target of the member.
194+
* @param memberUpdater Consumer that can updated the created member shape.
195+
* @return Returns the builder.
196+
*/
197+
public Builder value(ShapeId target, Consumer<MemberShape.Builder> memberUpdater) {
198+
if (getId() == null) {
199+
throw new IllegalStateException("An id must be set before setting a member with a target");
200+
}
201+
202+
MemberShape.Builder builder = MemberShape.builder()
203+
.target(target)
204+
.id(getId().withMember("value"));
205+
206+
if (memberUpdater != null) {
207+
memberUpdater.accept(builder);
208+
}
209+
210+
return value(builder.build());
211+
}
138212
}
139213
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.model.shapes;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.HashMap;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Optional;
25+
import java.util.TreeMap;
26+
import java.util.function.Consumer;
27+
28+
/**
29+
* Abstract classes shared by structure and union shapes.
30+
*/
31+
abstract class NamedMembersShape extends Shape {
32+
33+
private final Map<String, MemberShape> members;
34+
35+
NamedMembersShape(NamedMembersShape.Builder<?, ?> builder) {
36+
super(builder, false);
37+
assert builder.members != null;
38+
39+
// Copy the members to make them immutable and ensure that each
40+
// member has a valid ID that is prefixed with the shape ID.
41+
members = Collections.unmodifiableMap(new TreeMap<>(builder.members));
42+
43+
members.forEach((key, value) -> {
44+
ShapeId expected = getId().withMember(key);
45+
if (!value.getId().equals(expected)) {
46+
throw new IllegalArgumentException(String.format(
47+
"Expected the `%s` member of `%s` to have an ID of `%s` but found `%s`",
48+
key, getId(), expected, value.getId()));
49+
}
50+
});
51+
}
52+
53+
/**
54+
* Gets the members of the shape.
55+
*
56+
* @return Returns the immutable member map.
57+
*/
58+
public Map<String, MemberShape> getAllMembers() {
59+
return members;
60+
}
61+
62+
/**
63+
* Returns a list of member names.
64+
*
65+
* @return Returns list of member names.
66+
*/
67+
public List<String> getMemberNames() {
68+
return new ArrayList<>(members.keySet());
69+
}
70+
71+
/**
72+
* Get a specific member by name.
73+
*
74+
* @param name Name of the member to retrieve.
75+
* @return Returns the optional member.
76+
*/
77+
public Optional<MemberShape> getMember(String name) {
78+
return Optional.ofNullable(members.get(name));
79+
}
80+
81+
@Override
82+
public Collection<MemberShape> members() {
83+
return members.values();
84+
}
85+
86+
@Override
87+
public boolean equals(Object other) {
88+
return super.equals(other) && members.equals(((NamedMembersShape) other).members);
89+
}
90+
91+
/**
92+
* Builder used to create a List or Set shape.
93+
* @param <B> Concrete builder type.
94+
* @param <S> Shape type being created.
95+
*/
96+
abstract static class Builder<B extends Builder, S extends NamedMembersShape> extends AbstractShapeBuilder<B, S> {
97+
98+
Map<String, MemberShape> members = new HashMap<>();
99+
100+
/**
101+
* Replaces the members of the builder.
102+
*
103+
* @param members Members to add to the builder.
104+
* @return Returns the builder.
105+
*/
106+
@SuppressWarnings("unchecked")
107+
public B members(Collection<MemberShape> members) {
108+
this.members.clear();
109+
for (MemberShape member : members) {
110+
addMember(member);
111+
}
112+
return (B) this;
113+
}
114+
115+
/**
116+
* Removes all members from the shape.
117+
*
118+
* @return Returns the builder.
119+
*/
120+
@SuppressWarnings("unchecked")
121+
public B clearMembers() {
122+
members.clear();
123+
return (B) this;
124+
}
125+
126+
/**
127+
* Adds a member to the builder.
128+
*
129+
* @param member Shape targeted by the member.
130+
* @return Returns the builder.
131+
*/
132+
@Override
133+
@SuppressWarnings("unchecked")
134+
public B addMember(MemberShape member) {
135+
members.put(member.getMemberName(), member);
136+
return (B) this;
137+
}
138+
139+
/**
140+
* Adds a member to the builder.
141+
*
142+
* @param memberName Member name to add.
143+
* @param target Target of the member.
144+
* @return Returns the builder.
145+
*/
146+
public B addMember(String memberName, ShapeId target) {
147+
return addMember(memberName, target, null);
148+
}
149+
150+
/**
151+
* Adds a member to the builder.
152+
*
153+
* @param memberName Member name to add.
154+
* @param target Target of the member.
155+
* @param memberUpdater Consumer that can update the created member shape.
156+
* @return Returns the builder.
157+
*/
158+
public B addMember(String memberName, ShapeId target, Consumer<MemberShape.Builder> memberUpdater) {
159+
if (getId() == null) {
160+
throw new IllegalStateException("An id must be set before setting a member with a target");
161+
}
162+
163+
MemberShape.Builder builder = MemberShape.builder().target(target).id(getId().withMember(memberName));
164+
165+
if (memberUpdater != null) {
166+
memberUpdater.accept(builder);
167+
}
168+
169+
return addMember(builder.build());
170+
}
171+
172+
/**
173+
* Removes a member by name.
174+
*
175+
* @param member Member name to remove.
176+
* @return Returns the builder.
177+
*/
178+
@SuppressWarnings("unchecked")
179+
public B removeMember(String member) {
180+
members.remove(member);
181+
return (B) this;
182+
}
183+
}
184+
}

smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package software.amazon.smithy.model.shapes;
1717

18+
import java.util.Collection;
1819
import java.util.Collections;
1920
import java.util.List;
2021
import java.util.Map;
@@ -555,6 +556,15 @@ public final boolean isTimestampShape() {
555556
return getType() == ShapeType.TIMESTAMP;
556557
}
557558

559+
/**
560+
* Gets all of the members contained in the shape.
561+
*
562+
* @return Returns the members contained in the shape (if any).
563+
*/
564+
public Collection<MemberShape> members() {
565+
return Collections.emptyList();
566+
}
567+
558568
@Override
559569
public ShapeId toShapeId() {
560570
return id;

0 commit comments

Comments
 (0)