Skip to content

Commit

Permalink
generator-java: handle union types
Browse files Browse the repository at this point in the history
- Adds support for generating a shared super object type (issue #7)
- Add better union type handling in generator-java (issue #37)

fixes #7
fixes #37
  • Loading branch information
sonallux committed Mar 14, 2021
1 parent 9ea96b8 commit 76ce211
Show file tree
Hide file tree
Showing 27 changed files with 214 additions and 266 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.sonallux.spotify.core;

import de.sonallux.spotify.core.model.SpotifyWebApiObject;

import java.util.List;

public class SpotifyWebApiObjectUtils {
private static final List<String> BASE_OBJECT_PROPERTY_NAMES = List.of("id", "type", "href", "uri");
public static final List<String> BASE_OBJECT_NAMES = List.of("AlbumObject", "ArtistObject", "EpisodeObject", "PlaylistObject", "ShowObject", "TrackObject", "UserObject");
public static final String BASE_OBJECT_NAME = "BaseObject";
public static final SpotifyWebApiObject SPOTIFY_BASE_OBJECT = new SpotifyWebApiObject(BASE_OBJECT_NAME)
.addProperty(new SpotifyWebApiObject.Property("id", "String", "The [Spotify ID](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the object."))
.addProperty(new SpotifyWebApiObject.Property("type", "String", "The object type."))
.addProperty(new SpotifyWebApiObject.Property("href", "String", "A link to the Web API endpoint providing full details of the object."))
.addProperty(new SpotifyWebApiObject.Property("uri", "String", "The [Spotify URI](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids) for the object."));

public static boolean isBaseObject(SpotifyWebApiObject object) {
return object.getProperties().stream()
.filter(p -> BASE_OBJECT_PROPERTY_NAMES.contains(p.getName()))
.count() == BASE_OBJECT_PROPERTY_NAMES.size();
}

public static boolean removeBaseProperties(SpotifyWebApiObject object) {
if (BASE_OBJECT_NAME.equals(object.getName()) || !isBaseObject(object)) {
return false;
}
object.getProperties().removeIf(p -> BASE_OBJECT_PROPERTY_NAMES.contains(p.getName()));
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

var baseObjectTemplate = new BaseObjectTemplate().loadTemplate(this.mustacheFactory);
baseObjectTemplate.generate(SpotifyWebApiObjectUtils.SPOTIFY_BASE_OBJECT, outputFolder, javaPackage);

var objectTemplate = new ObjectTemplate().loadTemplate(this.mustacheFactory);
for (var object : spotifyWebApi.getObjectList()) {
objectTemplate.generate(object, outputFolder, javaPackage);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.sonallux.spotify.generator.java.templates;

import de.sonallux.spotify.core.SpotifyWebApiObjectUtils;
import de.sonallux.spotify.core.model.SpotifyWebApiObject;
import de.sonallux.spotify.generator.java.util.JavaUtils;

import java.util.Map;

public class BaseObjectTemplate extends ObjectTemplate {
@Override
public String templateName() {
return "base-object";
}

@Override
public String getFileName(SpotifyWebApiObject object) {
return JavaUtils.getFileName(SpotifyWebApiObjectUtils.BASE_OBJECT_NAME);
}

@Override
public Map<String, Object> buildContext(SpotifyWebApiObject object, Map<String, Object> rootContext) {
var context = super.buildContext(object, rootContext);
context.put("className", SpotifyWebApiObjectUtils.BASE_OBJECT_NAME);

return context;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package de.sonallux.spotify.generator.java.templates;

import com.google.common.base.CaseFormat;
import com.google.common.base.Strings;
import de.sonallux.spotify.core.SpotifyWebApiObjectUtils;
import de.sonallux.spotify.core.model.SpotifyWebApiObject;
import de.sonallux.spotify.generator.java.util.JavaPackage;
import de.sonallux.spotify.generator.java.util.JavaUtils;
Expand Down Expand Up @@ -34,6 +34,11 @@ public String getFileName(SpotifyWebApiObject object) {

@Override
public Map<String, Object> buildContext(SpotifyWebApiObject object, Map<String, Object> context) {
if (SpotifyWebApiObjectUtils.removeBaseProperties(object)) {
context.put("extendsBaseObject", true);
context.put("superClass", SpotifyWebApiObjectUtils.BASE_OBJECT_NAME);
}

context.put("name", object.getName());
context.put("className", getClassName(object));
context.put("properties", object.getProperties().stream().map(this::buildPropertyContext).collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.LOWER_UNDERSCORE;
import static de.sonallux.spotify.core.SpotifyWebApiObjectUtils.BASE_OBJECT_NAMES;

public class JavaUtils {
public static final List<String> RESERVED_WORDS = Arrays.asList(
Expand Down Expand Up @@ -64,6 +65,10 @@ public static String mapToJavaType(String type) {
} else if ((matcher = SpotifyWebApiUtils.CURSOR_PAGING_OBJECT_TYPE_PATTERN.matcher(type)).matches()) {
return "CursorPaging<" + mapToJavaType(matcher.group(1)) + ">";
} else if (type.contains(" | ")) {
if (Arrays.stream(type.split(" \\| "))
.allMatch(BASE_OBJECT_NAMES::contains)) {
return "BaseObject";
}
//Can not be mapped easily, so just use Map
return "java.util.Map<String, Object>";
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package {{package}};

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.*;

{{#documentationLink}}
/**
* <a href="{{documentationLink}}">{{name}}</a>
*/
{{/documentationLink}}
@Getter
@Setter
@NoArgsConstructor
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Album.class, name = "album"),
@JsonSubTypes.Type(value = Artist.class, name = "artist"),
@JsonSubTypes.Type(value = Episode.class, name = "episode"),
@JsonSubTypes.Type(value = Playlist.class, name = "playlist"),
@JsonSubTypes.Type(value = Show.class, name = "show"),
@JsonSubTypes.Type(value = Track.class, name = "track"),
@JsonSubTypes.Type(value = PrivateUser.class, name = "user"),
})
public abstract class {{className}} {
{{#properties}}
{{#hasDescription}}
/**
{{#description}}
* {{.}}
{{/description}}
*/
{{/hasDescription}}
{{#nonNull}}
@NonNull
{{/nonNull}}
{{#isReservedKeywordProperty}}
@lombok.experimental.Accessors(prefix = "_")
{{/isReservedKeywordProperty}}
public {{type}} {{fieldName}};
{{/properties}}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package {{package}};

{{#extendsBaseObject}}
import com.fasterxml.jackson.annotation.JsonTypeInfo;
{{/extendsBaseObject}}
import lombok.*;

{{#documentationLink}}
Expand All @@ -10,6 +13,9 @@ import lombok.*;
@Getter
@Setter
@NoArgsConstructor
{{#extendsBaseObject}}
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) // Disable deserialization based on @JsonTypeInfo
{{/extendsBaseObject}}
public class {{className}}{{#superClass}} extends {{superClass}}{{/superClass}} {
{{#properties}}
{{#hasDescription}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.sonallux.spotify.api.models;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.*;

/**
Expand All @@ -8,7 +9,8 @@
@Getter
@Setter
@NoArgsConstructor
public class Album {
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) // Disable deserialization based on @JsonTypeInfo
public class Album extends BaseObject {
/**
* <p>The type of the album: <code>album</code>, <code>single</code>, or <code>compilation</code>.</p>
*/
Expand Down Expand Up @@ -37,14 +39,6 @@ public class Album {
* <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>
*/
public java.util.List<String> genres;
/**
* <p>A link to the Web API endpoint providing full details of the album.</p>
*/
public String href;
/**
* <p>The Spotify ID for the album.</p>
*/
public String id;
/**
* <p>The cover art for the album in various sizes, widest first.</p>
*/
Expand Down Expand Up @@ -77,12 +71,4 @@ public class Album {
* <p>The tracks of the album.</p>
*/
public Paging<SimplifiedTrack> tracks;
/**
* <p>The object type: &quot;album&quot;</p>
*/
public String type;
/**
* <p>The Spotify URI for the album.</p>
*/
public String uri;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.sonallux.spotify.api.models;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.*;

/**
Expand All @@ -8,7 +9,8 @@
@Getter
@Setter
@NoArgsConstructor
public class Artist {
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) // Disable deserialization based on @JsonTypeInfo
public class Artist extends BaseObject {
/**
* <p>Known external URLs for this artist.</p>
*/
Expand All @@ -21,14 +23,6 @@ public class Artist {
* <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>
*/
public java.util.List<String> genres;
/**
* <p>A link to the Web API endpoint providing full details of the artist.</p>
*/
public String href;
/**
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify ID</a> for the artist.</p>
*/
public String id;
/**
* <p>Images of the artist in various sizes, widest first.</p>
*/
Expand All @@ -41,12 +35,4 @@ public class Artist {
* <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>
*/
public int popularity;
/**
* <p>The object type: <code>&quot;artist&quot;</code></p>
*/
public String type;
/**
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify URI</a> for the artist.</p>
*/
public String uri;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package de.sonallux.spotify.api.models;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Album.class, name = "album"),
@JsonSubTypes.Type(value = Artist.class, name = "artist"),
@JsonSubTypes.Type(value = Episode.class, name = "episode"),
@JsonSubTypes.Type(value = Playlist.class, name = "playlist"),
@JsonSubTypes.Type(value = Show.class, name = "show"),
@JsonSubTypes.Type(value = Track.class, name = "track"),
@JsonSubTypes.Type(value = PrivateUser.class, name = "user"),
})
public abstract class BaseObject {
/**
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify ID</a> for the object.</p>
*/
public String id;
/**
* <p>The object type.</p>
*/
public String type;
/**
* <p>A link to the Web API endpoint providing full details of the object.</p>
*/
public String href;
/**
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify URI</a> for the object.</p>
*/
public String uri;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class CurrentlyPlaying {
/**
* <p>The currently playing track or episode. Can be <code>null</code>.</p>
*/
public java.util.Map<String, Object> item;
public BaseObject item;
/**
* <p>Progress into the currently playing track or episode. Can be <code>null</code>.</p>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class CurrentlyPlayingContext {
/**
* <p>The currently playing track or episode. Can be <code>null</code>.</p>
*/
public java.util.Map<String, Object> item;
public BaseObject item;
/**
* <p>Progress into the currently playing track or episode. Can be <code>null</code>.</p>
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.sonallux.spotify.api.models;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.*;

/**
Expand All @@ -8,7 +9,8 @@
@Getter
@Setter
@NoArgsConstructor
public class Episode {
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) // Disable deserialization based on @JsonTypeInfo
public class Episode extends BaseObject {
/**
* <p>A URL to a 30 second preview (MP3 format) of the episode. <code>null</code> if not available.</p>
*/
Expand All @@ -29,14 +31,6 @@ public class Episode {
* <p>External URLs for this episode.</p>
*/
public ExternalUrl externalUrls;
/**
* <p>A link to the Web API endpoint providing full details of the episode.</p>
*/
public String href;
/**
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify ID</a> for the episode.</p>
*/
public String id;
/**
* <p>The cover art for the episode in various sizes, widest first.</p>
*/
Expand Down Expand Up @@ -77,12 +71,4 @@ public class Episode {
* <p>The show on which the episode belongs.</p>
*/
public SimplifiedShow show;
/**
* <p>The object type: &quot;episode&quot;.</p>
*/
public String type;
/**
* <p>The <a href="https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids">Spotify URI</a> for the episode.</p>
*/
public String uri;
}

0 comments on commit 76ce211

Please sign in to comment.