All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kohsuke.github.example.dataobject.ReadOnlyObjects Maven / Gradle / Ivy

There is a newer version: 2.0.0-alpha-2
Show newest version
package org.kohsuke.github.example.dataobject;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nonnull;

/**
 * {@link org.kohsuke.github.GHMeta} wraps the list of GitHub's IP addresses.
 * 

* This class is used to show examples of different ways to create simple read-only data objects. For data objects that * can be modified, perform actions, or get other objects we'll need other examples. *

* IMPORTANT: There is no one right way to do this, but there are better and worse. *

    *
  • Better: {@link GHMetaGettersUnmodifiable} is a good balance of clarity and brevity
  • *
  • Worse: {@link GHMetaPublic} exposes setters that are not needed, making it unclear that fields are actually * read-only
  • *
* * @author Liam Newman * @see org.kohsuke.github.GHMeta * @see Get Meta */ public final class ReadOnlyObjects { /** * All GHMeta data objects should expose these values. * * @author Liam Newman */ public interface GHMetaExample { /** * Is verifiable password authentication boolean. * * @return the boolean */ boolean isVerifiablePasswordAuthentication(); /** * Gets hooks. * * @return the hooks */ List getHooks(); /** * Gets git. * * @return the git */ List getGit(); /** * Gets web. * * @return the web */ List getWeb(); /** * Gets api. * * @return the api */ List getApi(); /** * Gets pages. * * @return the pages */ List getPages(); /** * Gets importer. * * @return the importer */ List getImporter(); } /** * This version uses public getters and setters and leaves it up to Jackson how it wants to fill them. *

* Pro: *

    *
  • Easy to create
  • *
  • Not much code
  • *
  • Minimal annotations
  • *
* Con: *
    *
  • Exposes public setters for fields that should not be changed, flagged by spotbugs
  • *
  • Lists modifiable when they should not be changed
  • *
  • Jackson generally doesn't call the setters, it just sets the fields directly
  • *
* * @author Paulo Miguel Almeida * @see org.kohsuke.github.GHMeta */ public static class GHMetaPublic implements GHMetaExample { @JsonProperty("verifiable_password_authentication") private boolean verifiablePasswordAuthentication; private List hooks; private List git; private List web; private List api; private List pages; private List importer; public boolean isVerifiablePasswordAuthentication() { return verifiablePasswordAuthentication; } /** * Sets verifiable password authentication. * * @param verifiablePasswordAuthentication * the verifiable password authentication */ public void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getHooks() { return hooks; } /** * Sets hooks. * * @param hooks * the hooks */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") public void setHooks(List hooks) { this.hooks = hooks; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getGit() { return git; } /** * Sets git. * * @param git * the git */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") public void setGit(List git) { this.git = git; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getWeb() { return web; } /** * Sets web. * * @param web * the web */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") public void setWeb(List web) { this.web = web; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getApi() { return api; } /** * Sets api. * * @param api * the api */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") public void setApi(List api) { this.api = api; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getPages() { return pages; } /** * Sets pages. * * @param pages * the pages */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") public void setPages(List pages) { this.pages = pages; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getImporter() { return importer; } /** * Sets importer. * * @param importer * the importer */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP2" }, justification = "Spotbugs also doesn't like this") public void setImporter(List importer) { this.importer = importer; } } /** * This version uses public getters and shows that package or private setters both can be used by jackson. You can * check this by running in debug and setting break points in the setters. * *

* Pro: *

    *
  • Easy to create
  • *
  • Not much code
  • *
  • Some annotations
  • *
* Con: *
    *
  • Exposes some package setters for fields that should not be changed, better than public
  • *
  • Lists modifiable when they should not be changed
  • *
* * @author Liam Newman * @see org.kohsuke.github.GHMeta */ public static class GHMetaPackage implements GHMetaExample { private boolean verifiablePasswordAuthentication; private List hooks; private List git; private List web; private List api; private List pages; /** * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. */ @JsonProperty private List importer; @JsonProperty("verifiable_password_authentication") public boolean isVerifiablePasswordAuthentication() { return verifiablePasswordAuthentication; } private void setVerifiablePasswordAuthentication(boolean verifiablePasswordAuthentication) { this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; } @JsonProperty @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getHooks() { return hooks; } /** * Setters can be private (or package local) and will still be called by Jackson. The {@link JsonProperty} can * got on the getter or setter and still work. * * @param hooks * list of hooks */ private void setHooks(List hooks) { this.hooks = hooks; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getGit() { return git; } /** * Since we mostly use Jackson for deserialization, {@link JsonSetter} is also okay, but {@link JsonProperty} is * preferred. * * @param git * list of git addresses */ @JsonSetter void setGit(List git) { this.git = git; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getWeb() { return web; } /** * The {@link JsonProperty} can got on the getter or setter and still work. * * @param web * list of web addresses */ void setWeb(List web) { this.web = web; } @JsonProperty @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getApi() { return api; } void setApi(List api) { this.api = api; } @JsonProperty @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getPages() { return pages; } void setPages(List pages) { this.pages = pages; } /** * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. * * @return list of importer addresses */ @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Noted above") public List getImporter() { return importer; } /** * Missing {@link JsonProperty} or having it on the field will cause Jackson to ignore getters and setters. * * @param importer * list of importer addresses */ void setImporter(List importer) { this.importer = importer; } } /** * This version uses only public getters and returns unmodifiable lists. * * *

* Pro: *

    *
  • Very Easy to create
  • *
  • Minimal code
  • *
  • Minimal annotations
  • *
  • Fields effectively final and lists unmodifiable
  • *
* Con: *
    *
  • Effectively final is not quite really final
  • *
  • If one of the lists were missing (an option member, for example), it will throw NPE but we could mitigate by * checking for null or assigning a default.
  • *
* * @author Liam Newman * @see org.kohsuke.github.GHMeta */ public static class GHMetaGettersUnmodifiable implements GHMetaExample { @JsonProperty("verifiable_password_authentication") private boolean verifiablePasswordAuthentication; private List hooks; private List git; private List web; private List api; private List pages; /** * If this were an optional member, we could fill it with an empty list by default. */ private List importer = new ArrayList<>(); public boolean isVerifiablePasswordAuthentication() { return verifiablePasswordAuthentication; } public List getHooks() { return Collections.unmodifiableList(hooks); } public List getGit() { return Collections.unmodifiableList(git); } public List getWeb() { return Collections.unmodifiableList(web); } public List getApi() { return Collections.unmodifiableList(api); } public List getPages() { return Collections.unmodifiableList(pages); } public List getImporter() { return Collections.unmodifiableList(importer); } } /** * This version uses only public getters and returns unmodifiable lists and has final fields *

* Pro: *

    *
  • Moderate amount of code
  • *
  • More annotations
  • *
  • Fields final and lists unmodifiable
  • *
* Con: *
    *
  • Extra allocations - default array lists will be replaced by Jackson (yes, even though they are final)
  • *
  • Added constructor is annoying
  • *
  • If this object could be refreshed or populated, then the final is misleading (and possibly buggy)
  • *
* * @author Liam Newman * @see org.kohsuke.github.GHMeta */ public static class GHMetaGettersFinal implements GHMetaExample { private final boolean verifiablePasswordAuthentication; private final List hooks = new ArrayList<>(); private final List git = new ArrayList<>(); private final List web = new ArrayList<>(); private final List api = new ArrayList<>(); private final List pages = new ArrayList<>(); private final List importer = new ArrayList<>(); @JsonCreator private GHMetaGettersFinal( @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { // boolean fields when final seem to be really final, so we have to switch to constructor this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; } public boolean isVerifiablePasswordAuthentication() { return verifiablePasswordAuthentication; } public List getHooks() { return Collections.unmodifiableList(hooks); } public List getGit() { return Collections.unmodifiableList(git); } public List getWeb() { return Collections.unmodifiableList(web); } public List getApi() { return Collections.unmodifiableList(api); } public List getPages() { return Collections.unmodifiableList(pages); } public List getImporter() { return Collections.unmodifiableList(importer); } } /** * This version uses only public getters and returns unmodifiable lists *

* Pro: *

    *
  • Fields final and lists unmodifiable
  • *
  • Construction behavior can be controlled - if values depended on each other or needed to be set in a specific * order, this could do that.
  • *
  • JsonProrperty "required" works on JsonCreator constructors - lets annotation define required values
  • *
* Con: *
    *
  • There is no way you'd know about this without some research
  • *
  • Specific annotations needed
  • *
  • Nonnull annotations are misleading - null value is not checked even for "required" constructor * parameters
  • *
  • Brittle and verbose - not friendly to large number of fields
  • *
* * @author Liam Newman * @see org.kohsuke.github.GHMeta */ public static class GHMetaGettersFinalCreator implements GHMetaExample { private final boolean verifiablePasswordAuthentication; private final List hooks; private final List git; private final List web; private final List api; private final List pages; private final List importer; /** * * @param hooks * the hooks - required property works, but only on creator json properties like this, ignores * Nonnull, checked manually * @param git * the git list - required property works, but only on creator json properties like this, misleading * Nonnull annotation * @param web * the web list - misleading Nonnull annotation * @param api * the api list - misleading Nonnull annotation * @param pages * the pages list - misleading Nonnull annotation * @param importer * the importer list - misleading Nonnull annotation * @param verifiablePasswordAuthentication * true or false */ @JsonCreator private GHMetaGettersFinalCreator(@Nonnull @JsonProperty(value = "hooks", required = true) List hooks, @Nonnull @JsonProperty(value = "git", required = true) List git, @Nonnull @JsonProperty("web") List web, @Nonnull @JsonProperty("api") List api, @Nonnull @JsonProperty("pages") List pages, @Nonnull @JsonProperty("importer") List importer, @JsonProperty("verifiable_password_authentication") boolean verifiablePasswordAuthentication) { // to ensure a value is actually not null we still have to do a null check Objects.requireNonNull(hooks); this.verifiablePasswordAuthentication = verifiablePasswordAuthentication; this.hooks = Collections.unmodifiableList(hooks); this.git = Collections.unmodifiableList(git); this.web = Collections.unmodifiableList(web); this.api = Collections.unmodifiableList(api); this.pages = Collections.unmodifiableList(pages); this.importer = Collections.unmodifiableList(importer); } public boolean isVerifiablePasswordAuthentication() { return verifiablePasswordAuthentication; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getHooks() { return hooks; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getGit() { return git; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getWeb() { return web; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getApi() { return api; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getPages() { return pages; } @SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Unmodifiable but spotbugs doesn't detect") public List getImporter() { return importer; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy