Skip to content

Commit 15bf374

Browse files
authored
generator-java: handle union types (#60)
- Adds support for generating a shared super object type (fixes #7) - Add better union type handling in generator-java (fixes #37)
1 parent 3c4047a commit 15bf374

File tree

27 files changed

+239
-266
lines changed

27 files changed

+239
-266
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package de.sonallux.spotify.core;
2+
3+
import de.sonallux.spotify.core.model.SpotifyWebApiObject;
4+
5+
import java.util.List;
6+
7+
public class SpotifyWebApiObjectUtils {
8+
private static final List<String> BASE_OBJECT_PROPERTY_NAMES = List.of("id", "type", "href", "uri");
9+
public static final List<String> BASE_OBJECT_NAMES = List.of("AlbumObject", "ArtistObject", "EpisodeObject", "PlaylistObject", "ShowObject", "TrackObject", "UserObject");
10+
public static final String BASE_OBJECT_NAME = "BaseObject";
11+
public static final SpotifyWebApiObject SPOTIFY_BASE_OBJECT = new SpotifyWebApiObject(BASE_OBJECT_NAME)
12+
.addProperty(new SpotifyWebApiObject.Property("id", "String", "The [Spotify ID](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the object."))
13+
.addProperty(new SpotifyWebApiObject.Property("type", "String", "The object type."))
14+
.addProperty(new SpotifyWebApiObject.Property("href", "String", "A link to the Web API endpoint providing full details of the object."))
15+
.addProperty(new SpotifyWebApiObject.Property("uri", "String", "The [Spotify URI](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the object."));
16+
17+
public static boolean isBaseObject(SpotifyWebApiObject object) {
18+
return object.getProperties().stream()
19+
.filter(p -> BASE_OBJECT_PROPERTY_NAMES.contains(p.getName()))
20+
.count() == BASE_OBJECT_PROPERTY_NAMES.size();
21+
}
22+
23+
public static boolean removeBaseProperties(SpotifyWebApiObject object) {
24+
if (BASE_OBJECT_NAME.equals(object.getName()) || !isBaseObject(object)) {
25+
return false;
26+
}
27+
object.getProperties().removeIf(p -> BASE_OBJECT_PROPERTY_NAMES.contains(p.getName()));
28+
return true;
29+
}
30+
}

spotify-web-api-generator-java/src/main/java/de/sonallux/spotify/generator/java/JavaGenerator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.mustachejava.MustacheFactory;
44
import de.sonallux.spotify.core.EndpointSplitter;
5+
import de.sonallux.spotify.core.SpotifyWebApiObjectUtils;
56
import de.sonallux.spotify.core.model.SpotifyWebApi;
67
import de.sonallux.spotify.generator.java.templates.*;
78
import de.sonallux.spotify.generator.java.util.JavaPackage;
@@ -25,6 +26,9 @@ public void generate(SpotifyWebApi spotifyWebApi, Path outputFolder, JavaPackage
2526
throw new GeneratorException("Failed to split endpoints", e);
2627
}
2728

29+
var baseObjectTemplate = new BaseObjectTemplate().loadTemplate(this.mustacheFactory);
30+
baseObjectTemplate.generate(SpotifyWebApiObjectUtils.SPOTIFY_BASE_OBJECT, outputFolder, javaPackage);
31+
2832
var objectTemplate = new ObjectTemplate().loadTemplate(this.mustacheFactory);
2933
for (var object : spotifyWebApi.getObjectList()) {
3034
objectTemplate.generate(object, outputFolder, javaPackage);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package de.sonallux.spotify.generator.java.templates;
2+
3+
import de.sonallux.spotify.core.SpotifyWebApiObjectUtils;
4+
import de.sonallux.spotify.core.model.SpotifyWebApiObject;
5+
import de.sonallux.spotify.generator.java.util.JavaUtils;
6+
7+
import java.util.Map;
8+
9+
public class BaseObjectTemplate extends ObjectTemplate {
10+
@Override
11+
public String templateName() {
12+
return "base-object";
13+
}
14+
15+
@Override
16+
public String getFileName(SpotifyWebApiObject object) {
17+
return JavaUtils.getFileName(SpotifyWebApiObjectUtils.BASE_OBJECT_NAME);
18+
}
19+
20+
@Override
21+
public Map<String, Object> buildContext(SpotifyWebApiObject object, Map<String, Object> rootContext) {
22+
var context = super.buildContext(object, rootContext);
23+
context.put("className", SpotifyWebApiObjectUtils.BASE_OBJECT_NAME);
24+
25+
return context;
26+
}
27+
}

spotify-web-api-generator-java/src/main/java/de/sonallux/spotify/generator/java/templates/ObjectTemplate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package de.sonallux.spotify.generator.java.templates;
22

33
import com.google.common.base.Strings;
4+
import de.sonallux.spotify.core.SpotifyWebApiObjectUtils;
45
import de.sonallux.spotify.core.model.SpotifyWebApiObject;
56
import de.sonallux.spotify.generator.java.util.JavaPackage;
67
import de.sonallux.spotify.generator.java.util.JavaUtils;
@@ -33,6 +34,11 @@ public String getFileName(SpotifyWebApiObject object) {
3334

3435
@Override
3536
public Map<String, Object> buildContext(SpotifyWebApiObject object, Map<String, Object> context) {
37+
if (SpotifyWebApiObjectUtils.removeBaseProperties(object)) {
38+
context.put("extendsBaseObject", true);
39+
context.put("superClass", SpotifyWebApiObjectUtils.BASE_OBJECT_NAME);
40+
}
41+
3642
context.put("name", object.getName());
3743
context.put("className", getClassName(object));
3844
context.put("properties", object.getProperties().stream().map(this::buildPropertyContext).collect(Collectors.toList()));

spotify-web-api-generator-java/src/main/java/de/sonallux/spotify/generator/java/util/JavaUtils.java

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

1111
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
1212
import static com.google.common.base.CaseFormat.LOWER_UNDERSCORE;
13+
import static de.sonallux.spotify.core.SpotifyWebApiObjectUtils.BASE_OBJECT_NAMES;
1314

1415
public class JavaUtils {
1516
public static final List<String> RESERVED_WORDS = Arrays.asList(
@@ -64,6 +65,10 @@ public static String mapToJavaType(String type) {
6465
} else if ((matcher = SpotifyWebApiUtils.CURSOR_PAGING_OBJECT_TYPE_PATTERN.matcher(type)).matches()) {
6566
return "CursorPaging<" + mapToJavaType(matcher.group(1)) + ">";
6667
} else if (type.contains(" | ")) {
68+
if (Arrays.stream(type.split(" \\| "))
69+
.allMatch(BASE_OBJECT_NAMES::contains)) {
70+
return "BaseObject";
71+
}
6772
//Can not be mapped easily, so just use Map
6873
return "java.util.Map<String, Object>";
6974
} else {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package {{package}};
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
5+
import lombok.*;
6+
7+
{{#documentationLink}}
8+
/**
9+
* <a href="{{documentationLink}}">{{name}}</a>
10+
*/
11+
{{/documentationLink}}
12+
@Getter
13+
@Setter
14+
@NoArgsConstructor
15+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
16+
@JsonSubTypes({
17+
@JsonSubTypes.Type(value = Album.class, name = "album"),
18+
@JsonSubTypes.Type(value = Artist.class, name = "artist"),
19+
@JsonSubTypes.Type(value = Episode.class, name = "episode"),
20+
@JsonSubTypes.Type(value = Playlist.class, name = "playlist"),
21+
@JsonSubTypes.Type(value = Show.class, name = "show"),
22+
@JsonSubTypes.Type(value = Track.class, name = "track"),
23+
@JsonSubTypes.Type(value = PrivateUser.class, name = "user"),
24+
})
25+
public abstract class {{className}} {
26+
{{#properties}}
27+
{{#hasDescription}}
28+
/**
29+
{{#description}}
30+
* {{.}}
31+
{{/description}}
32+
*/
33+
{{/hasDescription}}
34+
{{#nonNull}}
35+
@NonNull
36+
{{/nonNull}}
37+
{{#isReservedKeywordProperty}}
38+
@lombok.experimental.Accessors(prefix = "_")
39+
{{/isReservedKeywordProperty}}
40+
public {{type}} {{fieldName}};
41+
{{/properties}}
42+
}

spotify-web-api-generator-java/src/main/resources/templates/object.mustache

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package {{package}};
22

3+
{{#extendsBaseObject}}
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
5+
{{/extendsBaseObject}}
36
import lombok.*;
47

58
{{#documentationLink}}
@@ -10,6 +13,9 @@ import lombok.*;
1013
@Getter
1114
@Setter
1215
@NoArgsConstructor
16+
{{#extendsBaseObject}}
17+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) // Disable deserialization based on @JsonTypeInfo
18+
{{/extendsBaseObject}}
1319
public class {{className}}{{#superClass}} extends {{superClass}}{{/superClass}} {
1420
{{#properties}}
1521
{{#hasDescription}}

spotify-web-api-java/src/main/generated/de/sonallux/spotify/api/models/Album.java

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.sonallux.spotify.api.models;
22

3+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
34
import lombok.*;
45

56
/**
@@ -8,7 +9,8 @@
89
@Getter
910
@Setter
1011
@NoArgsConstructor
11-
public class Album {
12+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) // Disable deserialization based on @JsonTypeInfo
13+
public class Album extends BaseObject {
1214
/**
1315
* <p>The type of the album: <code>album</code>, <code>single</code>, or <code>compilation</code>.</p>
1416
*/
@@ -37,14 +39,6 @@ public class Album {
3739
* <p>A list of the genres used to classify the album. For example: &quot;Prog Rock&quot; , &quot;Post-Grunge&quot;. (If not yet classified, the array is empty.)</p>
3840
*/
3941
public java.util.List<String> genres;
40-
/**
41-
* <p>A link to the Web API endpoint providing full details of the album.</p>
42-
*/
43-
public String href;
44-
/**
45-
* <p>The Spotify ID for the album.</p>
46-
*/
47-
public String id;
4842
/**
4943
* <p>The cover art for the album in various sizes, widest first.</p>
5044
*/
@@ -77,12 +71,4 @@ public class Album {
7771
* <p>The tracks of the album.</p>
7872
*/
7973
public Paging<SimplifiedTrack> tracks;
80-
/**
81-
* <p>The object type: &quot;album&quot;</p>
82-
*/
83-
public String type;
84-
/**
85-
* <p>The Spotify URI for the album.</p>
86-
*/
87-
public String uri;
8874
}
Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.sonallux.spotify.api.models;
22

3+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
34
import lombok.*;
45

56
/**
@@ -8,7 +9,8 @@
89
@Getter
910
@Setter
1011
@NoArgsConstructor
11-
public class Artist {
12+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) // Disable deserialization based on @JsonTypeInfo
13+
public class Artist extends BaseObject {
1214
/**
1315
* <p>Known external URLs for this artist.</p>
1416
*/
@@ -21,14 +23,6 @@ public class Artist {
2123
* <p>A list of the genres the artist is associated with. For example: <code>&quot;Prog Rock&quot;</code> , <code>&quot;Post-Grunge&quot;</code>. (If not yet classified, the array is empty.)</p>
2224
*/
2325
public java.util.List<String> genres;
24-
/**
25-
* <p>A link to the Web API endpoint providing full details of the artist.</p>
26-
*/
27-
public String href;
28-
/**
29-
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify ID</a> for the artist.</p>
30-
*/
31-
public String id;
3226
/**
3327
* <p>Images of the artist in various sizes, widest first.</p>
3428
*/
@@ -41,12 +35,4 @@ public class Artist {
4135
* <p>The popularity of the artist. The value will be between 0 and 100, with 100 being the most popular. The artist's popularity is calculated from the popularity of all the artist's tracks.</p>
4236
*/
4337
public int popularity;
44-
/**
45-
* <p>The object type: <code>&quot;artist&quot;</code></p>
46-
*/
47-
public String type;
48-
/**
49-
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify URI</a> for the artist.</p>
50-
*/
51-
public String uri;
5238
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package de.sonallux.spotify.api.models;
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
5+
import lombok.*;
6+
7+
@Getter
8+
@Setter
9+
@NoArgsConstructor
10+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
11+
@JsonSubTypes({
12+
@JsonSubTypes.Type(value = Album.class, name = "album"),
13+
@JsonSubTypes.Type(value = Artist.class, name = "artist"),
14+
@JsonSubTypes.Type(value = Episode.class, name = "episode"),
15+
@JsonSubTypes.Type(value = Playlist.class, name = "playlist"),
16+
@JsonSubTypes.Type(value = Show.class, name = "show"),
17+
@JsonSubTypes.Type(value = Track.class, name = "track"),
18+
@JsonSubTypes.Type(value = PrivateUser.class, name = "user"),
19+
})
20+
public abstract class BaseObject {
21+
/**
22+
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify ID</a> for the object.</p>
23+
*/
24+
public String id;
25+
/**
26+
* <p>The object type.</p>
27+
*/
28+
public String type;
29+
/**
30+
* <p>A link to the Web API endpoint providing full details of the object.</p>
31+
*/
32+
public String href;
33+
/**
34+
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify URI</a> for the object.</p>
35+
*/
36+
public String uri;
37+
}

0 commit comments

Comments
 (0)