View Javadoc

1   package com.atlassian.marketplace.client.model;
2   
3   import java.net.URI;
4   import java.util.HashMap;
5   import java.util.Map;
6   
7   import com.atlassian.fugue.Option;
8   import com.atlassian.marketplace.client.MpacException;
9   import com.atlassian.marketplace.client.api.AddonCategoryId;
10  import com.atlassian.marketplace.client.api.AddonExternalLinkType;
11  import com.atlassian.marketplace.client.api.AddonVersionExternalLinkType;
12  import com.atlassian.marketplace.client.api.ApplicationKey;
13  import com.atlassian.marketplace.client.api.ArtifactId;
14  import com.atlassian.marketplace.client.api.ImageId;
15  import com.atlassian.marketplace.client.api.LicenseTypeId;
16  import com.atlassian.marketplace.client.api.UriTemplate;
17  import com.atlassian.marketplace.client.api.VendorExternalLinkType;
18  import com.atlassian.marketplace.client.api.VendorId;
19  import com.atlassian.marketplace.client.encoding.SchemaViolation;
20  import com.atlassian.marketplace.client.impl.SchemaViolationException;
21  import com.atlassian.marketplace.client.model.Screenshot.ScreenshotEmbedded;
22  import com.atlassian.marketplace.client.model.VersionCompatibility.CompatibilityHosting;
23  import com.atlassian.marketplace.client.model.VersionCompatibility.CompatibilityHostingBounds;
24  import com.atlassian.marketplace.client.util.EntityFunctions;
25  
26  import com.google.common.base.Function;
27  import com.google.common.base.Joiner;
28  import com.google.common.collect.ImmutableList;
29  import com.google.common.collect.ImmutableMap;
30  import com.google.common.collect.Iterables;
31  
32  import org.joda.time.LocalDate;
33  
34  import static com.atlassian.fugue.Option.none;
35  import static com.atlassian.fugue.Option.some;
36  import static com.atlassian.marketplace.client.api.ResourceId.resourceIdToUri;
37  import static com.atlassian.marketplace.client.impl.EntityValidator.validateInstance;
38  import static com.atlassian.marketplace.client.model.TestModelBuilders.DEFAULT_URI;
39  import static com.atlassian.marketplace.client.model.TestModelBuilders.addonDistributionSummary;
40  import static com.atlassian.marketplace.client.model.TestModelBuilders.addonReviewsSummary;
41  import static com.atlassian.marketplace.client.model.TestModelBuilders.imageInfo;
42  import static com.google.common.base.Preconditions.checkNotNull;
43  import static com.google.common.collect.Iterables.transform;
44  
45  /**
46   * Classes for constructing model objects for the 2.0 API.  Although model objects are usually
47   * returned to you by API methods, you may want to construct them yourself in order to create or
48   * update them on the server (such as creating a new add-on).  You should only ever construct
49   * these objects using the builders provided here; do not try to use their constructors directly.
50   * <p>
51   * The general pattern for using these builders is: 1. call a builder factory method, such as
52   * {@link #addon()}, which returns a builder of the appropriate type; 2. call property setter
53   * methods on the builder, such as {@link AddonBuilder#name(String)}; 3. after setting all the
54   * desired properties, call the builder's {@code build()} method, which returns the finished
55   * object.  Note that the builder factory methods can also be given an existing object as a
56   * parameter, in which case they initialize the builder by copying all of its properties from
57   * the existing object.
58   * <p>
59   * If you have omitted any required property, {@code build()} will throw an
60   * {@link InvalidModelException}.  (You can easily identify required properties because they
61   * do not use the {@code Option} type; properties in this library are never nullable, so if a
62   * property is a {@code String} rather than an {@code Option<String>}, it must have a value.)
63   * Optional properties always default to {@link Option#none()} if you do not specify a value.
64   * <p>
65   * For some very simple objects, the factory methods construct and return a finished object
66   * rather than returning a builder.
67   */
68  public abstract class ModelBuilders
69  {
70      private ModelBuilders()
71      {
72      }
73      
74      /**
75       * {@link ModelBuilders} methods that declare this checked exception will throw it if you attempt
76       * to construct a model object that does not conform to the Marketplace API schema, for instance
77       * by omitting a required property.
78       * @since 2.0.0
79       */
80      @SuppressWarnings("serial")
81      public static class InvalidModelException extends MpacException
82      {
83          private ImmutableList<SchemaViolation> schemaViolations;
84          
85          InvalidModelException(Iterable<SchemaViolation> schemaViolations)
86          {
87              this.schemaViolations = ImmutableList.copyOf(schemaViolations);
88          }
89          
90          public Iterable<SchemaViolation> getSchemaViolations()
91          {
92              return schemaViolations;
93          }
94          
95          @Override
96          public String getMessage()
97          {
98              return Joiner.on(", ").join(schemaViolations);
99          }
100     }
101     
102     /**
103      * Common interface for builders that will always succeed in building an object;
104      * any required properties that you do not set will be given default values.
105      * @param <T>  the type of the object being built
106      * @since 2.0.0
107      */
108     public static interface SafeBuilder<T>
109     {
110         T build();
111     }
112     
113     /**
114      * Common interface for builders that do not provide any default values for
115      * required properties, and will throw an exception if you try to call {@code build()}
116      * before you have set all the required properties.
117      * @param <T>  the type of the object being built
118      * @since 2.0.0
119      */
120     public static interface UnsafeBuilder<T>
121     {
122         T build() throws InvalidModelException;
123     }
124 
125     private static InvalidModelException modelException(SchemaViolationException e)
126     {
127         return new InvalidModelException(e.getSchemaViolations());
128     }
129     /**
130      * Creates a new {@link AddonBuilder}.
131      * @since 2.0.0
132      */
133     public static AddonBuilder addon()
134     {
135         return new AddonBuilder();
136     }
137 
138     /**
139      * Creates a new {@link AddonBuilder} initialized from the properties of an existing {@link Addon}.
140      * @since 2.0.0
141      */
142     public static AddonBuilder addon(Addon from)
143     {
144         return new AddonBuilder(from);
145     }
146 
147     /**
148      * Creates a new {@link AddonVersionBuilder}.
149      * @since 2.0.0
150      */
151     public static AddonVersionBuilder addonVersion()
152     {
153         return new AddonVersionBuilder();
154     }
155 
156     /**
157      * Creates a new {@link AddonVersionBuilder} initialized from the properties of an existing {@link AddonVersion}.
158      * @since 2.0.0
159      */
160     public static AddonVersionBuilder addonVersion(AddonVersion from)
161     {
162         return new AddonVersionBuilder(from);
163     }
164     
165     /**
166      * Creates a new {@link AddressBuilder}.
167      * @since 2.0.0
168      */
169     public static AddressBuilder address()
170     {
171         return new AddressBuilder();
172     }
173     
174     /**
175      * Creates a new {@link ApplicationVersionBuilder}.
176      * @since 2.0.0
177      */
178     public static ApplicationVersionBuilder applicationVersion()
179     {
180         return new ApplicationVersionBuilder();
181     }
182     
183     /**
184      * Creates a new {@link ApplicationVersionBuilder} initialized from the properties of an existing {@link ApplicationVersion}.
185      * @since 2.0.0
186      */
187     public static ApplicationVersionBuilder applicationVersion(ApplicationVersion from)
188     {
189         return new ApplicationVersionBuilder(from);
190     }
191     
192     /**
193      * Creates a new {@link HighlightBuilder}.
194      * @since 2.0.0
195      */
196     public static HighlightBuilder highlight()
197     {
198         return new HighlightBuilder();
199     }
200 
201     /**
202      * Creates a new {@link LinksBuilder}.
203      * @since 2.0.0
204      */
205     public static LinksBuilder links()
206     {
207         return new LinksBuilder();
208     }
209 
210     /**
211      * Creates a new {@link ScreenshotBuilder}.
212      * @since 2.0.0
213      */
214     public static ScreenshotBuilder screenshot()
215     {
216         return new ScreenshotBuilder();
217     }
218     
219     /**
220      * Creates a new {@link VendorBuilder}.
221      * @since 2.0.0
222      */
223     public static VendorBuilder vendor()
224     {
225         return new VendorBuilder();
226     }
227 
228     /**
229      * Creates a new {@link VendorBuilder} initialized from the properties of an existing {@link Vendor}.
230      * @since 2.0.0
231      */
232     public static VendorBuilder vendor(Vendor from)
233     {
234         return new VendorBuilder(from);
235     }
236     
237     /**
238      * Creates a {@link VersionCompatibility} that specifies compatibility with an application in
239      * Cloud hosting only.  For Cloud hosting, there is no application version range.
240      * @since 2.0.0
241      */
242     public static VersionCompatibility versionCompatibilityForCloud(ApplicationKey appKey)
243     {
244         return new VersionCompatibility(appKey,
245             new CompatibilityHosting(none(CompatibilityHostingBounds.class), some(true)));
246     }
247 
248     /**
249      * Creates a {@link VersionCompatibility} that specifies compatibility with an application in
250      * Server hosting only.  For Server hosting, you must specify the application version range as
251      * a minimum and maximum build number (this value comes from {@link ApplicationVersion#getBuildNumber()}).
252      * @since 2.0.0
253      */
254     public static VersionCompatibility versionCompatibilityForServer(ApplicationKey appKey,
255                                                                      int minBuild, int maxBuild)
256     {
257         return new VersionCompatibility(appKey,
258             new CompatibilityHosting(some(makeHostingBounds(minBuild, maxBuild)), some(false)));
259     }
260 
261     /**
262      * Creates a {@link VersionCompatibility} that specifies compatibility with an application in both
263      * Cloud and Server hosting, specifying the application version range for Server hosting as
264      * a minimum and maximum build number (this value comes from {@link ApplicationVersion#getBuildNumber()}).
265      * @since 2.0.0
266      */
267     public static VersionCompatibility versionCompatibilityForServerAndCloud(ApplicationKey appKey,
268                                                                              int minBuild, int maxBuild)
269     {
270         return new VersionCompatibility(appKey,
271             new CompatibilityHosting(some(makeHostingBounds(minBuild, maxBuild)), some(true)));
272     }
273 
274     private static CompatibilityHostingBounds makeHostingBounds(int min, int max)
275     {
276         return new CompatibilityHostingBounds(
277             new VersionCompatibility.VersionPoint(min, none(String.class)),
278             new VersionCompatibility.VersionPoint(max, none(String.class)));
279     }
280     
281     public static abstract class BuilderWithLinks<T extends BuilderWithLinks<T>>
282     {
283         protected LinksBuilder links = new LinksBuilder();
284      
285         protected BuilderWithLinks()
286         {
287             // All model entities must have a "self" link.  This link is always generated by the server and
288             // is ignored in any object that is posted to the server; we provide a default for it here so
289             // newly created objects will never fail validation due to the lack of it.
290             links.put("self", TestModelBuilders.DEFAULT_URI);
291         }
292      
293         @SuppressWarnings("unchecked")
294         public T links(Links links)
295         {
296             this.links.removeAll();
297             this.links.put(links);
298             return (T) this;
299         }
300         
301         @SuppressWarnings("unchecked")
302         public T addLinks(Links links)
303         {
304             this.links.put(links);
305             return (T) this;
306         }
307         
308         @SuppressWarnings("unchecked")
309         public T addLink(String rel, URI uri)
310         {
311             this.links.put(rel, uri);
312             return (T) this;
313         }
314         
315         @SuppressWarnings("unchecked")
316         public T addLink(String rel, String type, URI uri)
317         {
318             this.links.put(rel, some(type), uri);
319             return (T) this;
320         }
321         
322         @SuppressWarnings("unchecked")
323         public T addLinkTemplate(String rel, String template)
324         {
325             this.links.putTemplate(rel, template);
326             return (T) this;
327         }
328     }
329     
330     public static abstract class UnsafeBuilderWithLinks<A, T extends UnsafeBuilderWithLinks<A, T>>
331         extends BuilderWithLinks<T> implements UnsafeBuilder<A>
332     {
333         protected abstract A buildUnsafe();
334         
335         public A build() throws InvalidModelException
336         {
337             try
338             {
339                 return buildUnsafe();
340             }
341             catch (SchemaViolationException e)
342             {
343                 throw modelException(e);
344             }
345         }
346     }
347     
348     public static class AddonBuilder extends UnsafeBuilderWithLinks<Addon, AddonBuilder>
349     {
350         private String name;
351         private String key;
352         private AddonStatus status;
353         private Option<String> summary = none();
354         private Option<String> tagLine = none();
355         private Option<AddonVersion> version = none();
356         private Option<Boolean> enableAtlassianAnswers = none();
357         private Map<String, URI> externalLinks = new HashMap<String, URI>();
358         
359         private AddonBuilder()
360         {
361             links.put("alternate", TestModelBuilders.DEFAULT_URI);
362         }
363         
364         private AddonBuilder(Addon from)
365         {
366             links.put(from.getLinks());
367             name = from.getName();
368             key = from.getKey();
369             status = from.getStatus();
370             summary = from.getSummary();
371             tagLine = from.getTagLine();
372             version = from.getVersion();
373             enableAtlassianAnswers = from.isEnableAtlassianAnswers();
374             for (AddonExternalLinkType type: AddonExternalLinkType.values())
375             {
376                 for (URI u: from.getExternalLinkUri(type))
377                 {
378                     externalLinks.put(type.getKey(), u);
379                 }
380             }
381         }
382         
383         protected Addon buildUnsafe()
384         {
385             Addon addon = new Addon();
386             addon._links = links.build();
387             
388             addon._embedded = new Addon.Embedded();
389             addon._embedded.banner = none();
390             addon._embedded.logo = none();
391             addon._embedded.categories = ImmutableList.of();
392             addon._embedded.distribution = addonDistributionSummary().build();
393             addon._embedded.reviews = addonReviewsSummary(0, 0);
394             addon._embedded.vendor = none();
395             addon._embedded.version = version;
396             addon._embedded = validateInstance(addon._embedded);
397             
398             addon.name = name;
399             addon.key = key;
400             addon.status = status;
401             addon.summary = summary;
402             addon.tagLine = tagLine;
403             addon.cloudFreeUsers = none();
404             addon.legacy = none();
405             addon.status = status;
406             addon.enableAtlassianAnswers = enableAtlassianAnswers;
407             addon.vendorLinks = ImmutableMap.copyOf(externalLinks);
408             
409             return validateInstance(addon);
410         }
411         
412         /**
413          * @see AddonBase#getName()
414          */
415         public AddonBuilder name(String name)
416         {
417             this.name = checkNotNull(name);
418             return this;
419         }
420         
421         /**
422          * @see AddonBase#getKey()
423          */
424         public AddonBuilder key(String key)
425         {
426             this.key = checkNotNull(key);
427             return this;
428         }
429         
430         /**
431          * @see AddonBase#getStatus()
432          */
433         public AddonBuilder status(AddonStatus status)
434         {
435             this.status = status;
436             return this;
437         }
438 
439         /**
440          * @see AddonBase#getSummary()
441          */
442         public AddonBuilder summary(Option<String> summary)
443         {
444             this.summary = checkNotNull(summary);
445             return this;
446         }
447 
448         /**
449          * @see AddonBase#getTagLine()
450          */
451         public AddonBuilder tagLine(Option<String> tagLine)
452         {
453             this.tagLine = checkNotNull(tagLine);
454             return this;
455         }
456 
457         /**
458          * @see Addon#getBanner()
459          */
460         public AddonBuilder banner(Option<ImageId> image)
461         {
462             links.put("banner", image.map(resourceIdToUri()));
463             return this;
464         }
465         
466         /**
467          * @see AddonBase#getLogo()
468          */
469         public AddonBuilder logo(Option<ImageId> image)
470         {
471             links.put("logo", image.map(resourceIdToUri()));
472             return this;
473         }
474         
475         /**
476          * @see AddonBase#getCategoryIds()
477          */
478         public AddonBuilder categories(Iterable<AddonCategoryId> categories)
479         {
480             links.put("categories", transform(categories, resourceIdToUri()));
481             return this;
482         }
483 
484         /**
485          * Specifies the add-on's vendor using a {@link VendorSummary} instance.
486          * @see #vendor(VendorId)
487          */
488         public AddonBuilder vendor(VendorSummary vendor)
489         {
490             return vendor(vendor.getId());
491         }
492 
493         /**
494          * @see AddonBase#getVendorId()
495          * @see VendorBase#getId()
496          * @see #vendor(VendorSummary) 
497          */
498         public AddonBuilder vendor(VendorId id)
499         {
500             links.put("vendor", id.getUri());
501             return this;
502         }
503 
504         /**
505          * @see Addon#isEnableAtlassianAnswers()
506          */
507         public AddonBuilder enableAtlassianAnswers(boolean enableAtlassianAnswers)
508         {
509             this.enableAtlassianAnswers = some(enableAtlassianAnswers);
510             return this;
511         }
512         
513         /**
514          * Attaches an {@link AddonVersion} to the {@link Addon}.  You must do this if you are
515          * creating a new add-on, since an add-on cannot be created with no versions.
516          * @see Addon#getVersion()
517          */
518         public AddonBuilder version(Option<AddonVersion> version)
519         {
520             this.version = checkNotNull(version);
521             return this;
522         }
523         
524         /**
525          * Sets one of the vendor-specified external links for the add-on.
526          * @param type an {@link AddonExternalLinkType}
527          * @param uri the optional link URI
528          * @see Addon#getExternalLinkUri(AddonExternalLinkType)
529          */
530         public AddonBuilder externalLinkUri(AddonExternalLinkType type, Option<URI> uri)
531         {
532             if (!type.canSetForNewAddons())
533             {
534                 throw new IllegalArgumentException("Cannot set " + type + " link for new add-ons");
535             }
536             externalLinks.remove(type.getKey());
537             for (URI u: uri)
538             {
539                 externalLinks.put(type.getKey(), u);
540             }
541             return this;
542         }
543     }
544     
545     public static class AddonVersionBuilder extends UnsafeBuilderWithLinks<AddonVersion, AddonVersionBuilder>
546     {
547         private Integer buildNumber;
548         private Option<String> name = none();
549         private AddonVersionStatus status;
550         private PaymentModel paymentModel;
551         private Option<ImmutableList<Highlight>> highlights = none();
552         private Option<ImmutableList<Screenshot>> screenshots = none();
553         private Option<String> youtubeId = none();
554         private Option<ImmutableList<VersionCompatibility>> compatibilities = none();
555         private LocalDate releaseDate;
556         private Option<String> releasedBy = none();
557         private boolean beta = false;
558         private boolean staticAddon = false;
559         private boolean supported = false;
560         private boolean deployable = false;
561         private Option<String> releaseSummary = none();
562         private Option<HtmlString> moreDetails = none();
563         private Option<HtmlString> releaseNotes = none();
564         private Map<String, URI> externalLinks = new HashMap<String, URI>();
565         
566         private AddonVersionBuilder()
567         {
568         }
569         
570         private AddonVersionBuilder(AddonVersion from)
571         {
572             links.put(from.getLinks());
573             buildNumber = from.getBuildNumber();
574             name = from.getName();
575             status = from.getStatus();
576             paymentModel = from.getPaymentModel();
577             releaseDate = from.getReleaseDate();
578             releasedBy = from.getReleasedBy();
579             highlights = copyOptionalList(from.getHighlightsIfSpecified());
580             screenshots = copyOptionalList(from.getScreenshotsIfSpecified());
581             youtubeId = from.getYoutubeId();
582             compatibilities = copyOptionalList(from.getCompatibilitiesIfSpecified());
583             beta = from.isBeta();
584             supported = from.isSupported();
585             staticAddon = from.isStatic();
586             deployable = from.isDeployable();
587             releaseSummary = from.getReleaseSummary();
588             moreDetails = from.getMoreDetails();
589             releaseNotes = from.getReleaseNotes();
590             for (AddonVersionExternalLinkType type: AddonVersionExternalLinkType.values())
591             {
592                 for (URI u: from.getExternalLinkUri(type))
593                 {
594                     externalLinks.put(type.getKey(), u);
595                 }
596             }
597         }
598         
599         protected AddonVersion buildUnsafe()
600         {
601             AddonVersion ret = new AddonVersion();
602             ret._links = links.build();
603             ret._embedded = new AddonVersion.Embedded();
604             ret._embedded.artifact = none();
605             ret._embedded.highlights = highlights;
606             ret._embedded.screenshots = screenshots;
607             ret.buildNumber = buildNumber;
608             ret.name = name;
609             ret.status = status;
610             ret.paymentModel = paymentModel;
611             ret.youtubeId = youtubeId;
612             ret.compatibilities = compatibilities;
613             ret.staticAddon = staticAddon;
614             ret.deployable = deployable;
615             ret.deployment = new AddonVersion.DeploymentProperties();
616             ret.deployment.autoUpdateAllowed = false;
617             ret.deployment.cloud = false;
618             ret.deployment.connect = false;
619             ret.deployment.dataCenter = false;
620             ret.deployment.permissions = none();
621             ret.deployment.server = false;
622             ret.legacy = none();
623             AddonVersionBase.ReleaseProperties release = new AddonVersionBase.ReleaseProperties();
624             release.beta = beta;
625             release.date = releaseDate;
626             release.releasedBy = releasedBy;
627             release.supported = supported;
628             ret.release = validateInstance(release);
629             ret.text = new AddonVersion.TextProperties();
630             ret.text.releaseSummary = releaseSummary;
631             ret.text.moreDetails = moreDetails;
632             ret.text.releaseNotes = releaseNotes;
633             ret.text = validateInstance(ret.text);
634             ret.vendorLinks = ImmutableMap.copyOf(externalLinks);
635             return validateInstance(ret);
636         }
637         
638         /**
639          * @see AddonVersionBase#getArtifactInfo()
640          * @see AddonVersionBase#getArtifactUri()
641          */
642         public AddonVersionBuilder artifact(Option<ArtifactId> artifact)
643         {
644             links.put("artifact", artifact.map(resourceIdToUri()));
645             return this;
646         }
647         
648         /**
649          * Specifies the version's build number, a value that distinguishes it from all other versions
650          * of the add-on and determines the correct ordering of versions.  You must specify this for
651          * all new versions-- although in the case of an Atlassian Connect add-on, the value you set
652          * is arbitrary since Marketplace will replace it with an automatically computed value.  The
653          * build number cannot be changed after a version is created.
654          * @see AddonVersion#getBuildNumber()
655          */
656         public AddonVersionBuilder buildNumber(int buildNumber)
657         {
658             this.buildNumber = buildNumber;
659             return this;
660         }
661         
662         /**
663          * Specifies the version name (a.k.a. version string), such as "1.0".  You can omit this if you
664          * are creating a new version that is either 1. an installable jar/obr (since Marketplace can
665          * find the version string from its {@code atlassian-plugin.xml}) or 2. an Atlassian Connect
666          * plugin (since Marketplace automatically generates version strings for these).
667          * @see AddonVersionBase#getName()
668          */
669         public AddonVersionBuilder name(String name)
670         {
671             this.name = some(name);
672             return this;
673         }
674         
675         /**
676          * @see AddonVersionBase#getStatus()
677          */
678         public AddonVersionBuilder status(AddonVersionStatus status)
679         {
680             this.status = checkNotNull(status);
681             return this;
682         }
683 
684         /**
685          * @see AddonVersion#getLicenseType()
686          */
687         public AddonVersionBuilder licenseType(Option<LicenseType> licenseType)
688         {
689             links.put("license", licenseType.flatMap(EntityFunctions.selfUri()));
690             return this;
691         }
692 
693         /**
694          * @see AddonVersion#getLicenseTypeId()
695          */
696         public AddonVersionBuilder licenseTypeId(Option<LicenseTypeId> licenseTypeId)
697         {
698             links.put("license", licenseTypeId.map(resourceIdToUri()));
699             return this;
700         }
701         
702         /**
703          * @see AddonVersionBase#getPaymentModel()
704          */
705         public AddonVersionBuilder paymentModel(PaymentModel paymentModel)
706         {
707             this.paymentModel = checkNotNull(paymentModel);
708             return this;
709         }
710         
711         /**
712          * @see AddonVersionBase#getReleaseDate()
713          */
714         public AddonVersionBuilder releaseDate(LocalDate releaseDate)
715         {
716             this.releaseDate = checkNotNull(releaseDate);
717             return this;
718         }
719 
720         /**
721          * @see AddonVersionBase#getReleasedBy()
722          */
723         public AddonVersionBuilder releasedBy(Option<String> releasedBy)
724         {
725             this.releasedBy = checkNotNull(releasedBy);
726             return this;
727         }
728         
729         /**
730          * @see AddonVersionBase#getHighlights()
731          */
732         public AddonVersionBuilder highlights(Iterable<Highlight> highlights)
733         {
734             this.highlights = some(ImmutableList.copyOf(highlights));
735             return this;
736         }
737         
738         /**
739          * @see AddonVersionBase#getScreenshots()
740          */
741         public AddonVersionBuilder screenshots(Iterable<Screenshot> screenshots)
742         {
743             this.screenshots = some(ImmutableList.copyOf(screenshots));
744             return this;
745         }
746         
747         /**
748          * @see AddonVersionBase#getYoutubeId()
749          */
750         public AddonVersionBuilder youtubeId(Option<String> youtubeId)
751         {
752             this.youtubeId = youtubeId;
753             return this;
754         }
755         
756         /**
757          * @see AddonVersionBase#getCompatibilities()
758          */
759         public AddonVersionBuilder compatibilities(Iterable<VersionCompatibility> compatibilities)
760         {
761             this.compatibilities = some(ImmutableList.copyOf(compatibilities));
762             return this;
763         }
764 
765         /**
766          * @see AddonVersionBase#isBeta()
767          */
768         public AddonVersionBuilder beta(boolean beta)
769         {
770             this.beta = beta;
771             return this;
772         }
773         
774         /**
775          * @see AddonVersionBase#isSupported()
776          */
777         public AddonVersionBuilder supported(boolean supported)
778         {
779             this.supported = supported;
780             return this;
781         }
782         
783         /**
784          * @see AddonVersionBase#isStaticAddon()
785          */
786         public AddonVersionBuilder staticAddon(boolean staticAddon)
787         {
788             this.staticAddon = staticAddon;
789             return this;
790         }
791 
792         /**
793          * @see AddonVersionBase#isDeployable()
794          */
795         public AddonVersionBuilder deployable(boolean deployable)
796         {
797             this.deployable = deployable;
798             return this;
799         }
800 
801         /**
802          * @see AddonVersion#getReleaseSummary()
803          */
804         public AddonVersionBuilder releaseSummary(Option<String> releaseSummary)
805         {
806             this.releaseSummary = releaseSummary;
807             return this;
808         }
809         
810         /**
811          * @see AddonVersion#getMoreDetails()
812          */
813         public AddonVersionBuilder moreDetails(Option<HtmlString> moreDetails)
814         {
815             this.moreDetails = moreDetails;
816             return this;
817         }
818         
819         /**
820          * @see AddonVersion#getReleaseNotes()
821          */
822         public AddonVersionBuilder releaseNotes(Option<HtmlString> releaseNotes)
823         {
824             this.releaseNotes = releaseNotes;
825             return this;
826         }
827 
828         /**
829          * Sets one of the vendor-specified external links for the add-on version.
830          * @param type an {@link AddonVersionExternalLinkType}
831          * @param uri the optional link URI
832          * @see AddonVersion#getExternalLinkUri(AddonVersionExternalLinkType)
833          */
834         public AddonVersionBuilder externalLinkUri(AddonVersionExternalLinkType type, Option<URI> uri)
835         {
836             if (!type.canSetForNewAddonVersions())
837             {
838                 throw new IllegalArgumentException("Cannot set " + type + " link for new add-ons");
839             }
840             externalLinks.remove(type.getKey());
841             for (URI u: uri)
842             {
843                 externalLinks.put(type.getKey(), u);
844             }
845             return this;
846         }
847     }
848     
849     public static class AddressBuilder implements UnsafeBuilder<Address>
850     {
851         private String line1;
852         private Option<String> line2 = none();
853         private Option<String> city = none();
854         private Option<String> state = none();
855         private Option<String> postCode = none();
856         private Option<String> country = none();
857         
858         public Address build() throws InvalidModelException
859         {
860             try
861             {
862                 Address ret = new Address();
863                 ret.line1 = line1;
864                 ret.line2 = line2;
865                 ret.city = city;
866                 ret.state = state;
867                 ret.postCode = postCode;
868                 ret.country = country;
869                 return validateInstance(ret);
870             }
871             catch (SchemaViolationException e)
872             {
873                 throw modelException(e);
874             }
875         }
876         
877         /**
878          * @see Address#getLine1()
879          */
880         public AddressBuilder line1(String line1)
881         {
882             this.line1 = checkNotNull(line1);
883             return this;
884         }
885         
886         /**
887          * @see Address#getLine2()
888          */
889         public AddressBuilder line2(Option<String> line2)
890         {
891             this.line2 = checkNotNull(line2);
892             return this;
893         }
894         
895         /**
896          * @see Address#getCity()
897          */
898         public AddressBuilder city(Option<String> city)
899         {
900             this.city = checkNotNull(city);
901             return this;
902         }
903         
904         /**
905          * @see Address#getState()
906          */
907         public AddressBuilder state(Option<String> state)
908         {
909             this.state = checkNotNull(state);
910             return this;
911         }
912         
913         /**
914          * @see Address#getPostCode()
915          */
916         public AddressBuilder postCode(Option<String> postCode)
917         {
918             this.postCode = checkNotNull(postCode);
919             return this;
920         }
921         
922         /**
923          * @see Address#getCountry()
924          */
925         public AddressBuilder country(Option<String> country)
926         {
927             this.country = checkNotNull(country);
928             return this;
929         }
930     }
931     
932     public static class ApplicationVersionBuilder extends UnsafeBuilderWithLinks<ApplicationVersion, ApplicationVersionBuilder>
933     {
934         private Integer buildNumber;
935         private String name;
936         private LocalDate releaseDate;
937         private ApplicationVersionStatus status;
938         
939         private ApplicationVersionBuilder()
940         {
941         }
942         
943         private ApplicationVersionBuilder(ApplicationVersion from)
944         {
945             links.put(from.getLinks());
946             buildNumber = from.getBuildNumber();
947             name = from.getName();
948             releaseDate = from.getReleaseDate();
949             status = from.getStatus();
950         }
951         
952         protected ApplicationVersion buildUnsafe()
953         {
954             ApplicationVersion ret = new ApplicationVersion();
955             ret._links = links.build();
956             ret.buildNumber = buildNumber;
957             ret.version = name;
958             ret.releaseDate = releaseDate;
959             ret.status = status;
960             return validateInstance(ret);
961         }
962         
963         /**
964          * @see ApplicationVersion#getBuildNumber()
965          */
966         public ApplicationVersionBuilder buildNumber(int buildNumber)
967         {
968             this.buildNumber = buildNumber;
969             return this;
970         }
971         
972         /**
973          * @see ApplicationVersion#getName()
974          */
975         public ApplicationVersionBuilder name(String name)
976         {
977             this.name = checkNotNull(name);
978             return this;
979         }
980         
981         /**
982          * @see ApplicationVersion#getReleaseDate()
983          */
984         public ApplicationVersionBuilder releaseDate(LocalDate releaseDate)
985         {
986             this.releaseDate = checkNotNull(releaseDate);
987             return this;
988         }
989         
990         /**
991          * @see ApplicationVersion#getStatus()
992          */
993         public ApplicationVersionBuilder status(ApplicationVersionStatus status)
994         {
995             this.status = checkNotNull(status);
996             return this;
997         }
998     }
999     
1000     public static class HighlightBuilder extends UnsafeBuilderWithLinks<Highlight, HighlightBuilder>
1001     {
1002         private ImageInfo fullImage;
1003         private ImageInfo thumbnailImage;
1004         private String title;
1005         private HtmlString body;
1006         private Option<String> explanation = none();
1007         
1008         protected Highlight buildUnsafe()
1009         {
1010             Highlight ret = new Highlight();
1011             ret._embedded = new Highlight.Embedded();
1012             ret._embedded.screenshot = fullImage;
1013             ret._embedded.thumbnail = thumbnailImage;
1014             ret._embedded = validateInstance(ret._embedded);
1015             ret._links = links.build();
1016             ret.body = body;
1017             ret.title = title;
1018             ret.explanation = explanation;
1019             return validateInstance(ret);
1020         }
1021         
1022         /**
1023          * @see Highlight#getFullImage()
1024          */
1025         public HighlightBuilder fullImage(ImageId fullImage)
1026         {
1027             links.put("screenshot", fullImage.getUri());
1028             this.fullImage = imageInfo().build();
1029             return this;
1030         }
1031 
1032         /**
1033          * @see Highlight#getThumbnailImage()
1034          */
1035         public HighlightBuilder thumbnailImage(ImageId thumbnailImage)
1036         {
1037             links.put("thumbnail", thumbnailImage.getUri());
1038             this.thumbnailImage = imageInfo().build();
1039             return this;
1040         }
1041 
1042         /**
1043          * @see Highlight#getTitle()
1044          */
1045         public HighlightBuilder title(String title)
1046         {
1047             this.title = checkNotNull(title);
1048             return this;
1049         }
1050 
1051         /**
1052          * @see Highlight#getBody()
1053          */
1054         public HighlightBuilder body(HtmlString body)
1055         {
1056             this.body = checkNotNull(body);
1057             return this;
1058         }
1059 
1060         /**
1061          * @see Highlight#getExplanation()
1062          */
1063         public HighlightBuilder explanation(Option<String> explanation)
1064         {
1065             this.explanation = checkNotNull(explanation);
1066             return this;
1067         }
1068     }
1069     
1070     public static class LinksBuilder implements SafeBuilder<Links>
1071     {
1072         private Map<String, ImmutableList<Link>> links = new HashMap<String, ImmutableList<Link>>();
1073 
1074         public Links build()
1075         {
1076             return new Links(links);
1077         }
1078 
1079         /**
1080          * Adds a regular resource link, replacing any other links with the same key.
1081          */
1082         public LinksBuilder put(String rel, URI uri)
1083         {
1084             return put(rel, none(String.class), uri);
1085         }
1086 
1087         /**
1088          * Adds a list of regular resource links for a single link rel, replacing any other links with
1089          * the same key.
1090          */
1091         public LinksBuilder put(String rel, Iterable<URI> uris)
1092         {
1093             return put(rel, ImmutableList.copyOf(Iterables.transform(uris, new Function<URI, Link>()
1094                 {
1095                     public Link apply(URI uri)
1096                     {
1097                         return Link.fromUri(uri, none(String.class));
1098                     }
1099                 })));
1100         }
1101 
1102         /**
1103          * Adds a link with an optional content type, replacing any other links with the same key.
1104          */
1105         public LinksBuilder put(String rel, Option<String> type, URI uri)
1106         {
1107             return put(rel, ImmutableList.of(Link.fromUri(uri, type)));
1108         }
1109 
1110         /**
1111          * Optionally adds a resource link, making no change if it is {@link Option#none()}.
1112          */
1113         public LinksBuilder put(String rel, Option<URI> maybeUri)
1114         {
1115             for (URI uri: maybeUri)
1116             {
1117                 return put(rel, uri);
1118             }
1119             return this;
1120         }
1121         
1122         /**
1123          * Adds a list of links of any kind, replacing any other links with the same key.
1124          */
1125         public LinksBuilder put(String rel, ImmutableList<Link> values)
1126         {
1127             links.put(rel, values);
1128             return this;
1129         }
1130 
1131         /**
1132          * Copies all the links from another {@link Links} object, replacing any links with the same
1133          * key.
1134          */
1135         public LinksBuilder put(Links from)
1136         {
1137             links.putAll(from.getItems());
1138             return this;
1139         }
1140         
1141         /**
1142          * Adds a link template, replacing any other links with the same key.
1143          */
1144         public LinksBuilder putTemplate(String rel, String template)
1145         {
1146             return put(rel, ImmutableList.of(Link.fromUriTemplate(UriTemplate.create(template), none(String.class))));
1147         }
1148         
1149         /**
1150          * Removes any links with the specified key.
1151          */
1152         public LinksBuilder remove(String rel)
1153         {
1154             links.remove(rel);
1155             return this;
1156         }
1157         
1158         /**
1159          * Removes all links.
1160          */
1161         public LinksBuilder removeAll()
1162         {
1163             links.clear();
1164             return this;
1165         }
1166     }
1167     
1168     public static class ScreenshotBuilder extends UnsafeBuilderWithLinks<Screenshot, ScreenshotBuilder>
1169     {
1170         private ImageInfo image;
1171         private Option<String> caption = none();
1172 
1173         protected Screenshot buildUnsafe()
1174         {
1175             Screenshot ret = new Screenshot();
1176             ret._links = links.build();
1177             ret._embedded = new ScreenshotEmbedded();
1178             ret._embedded.image = image;
1179             ret._embedded = validateInstance(ret._embedded);
1180             ret.caption = caption;
1181             return validateInstance(ret);
1182         }
1183 
1184         /**
1185          * @see Screenshot#getImage()
1186          */
1187         public ScreenshotBuilder image(ImageId image)
1188         {
1189             links.put("image", image.getUri());
1190             this.image = imageInfo().build();
1191             return this;
1192         }
1193         
1194         /**
1195          * @see Screenshot#getCaption()
1196          */
1197         public ScreenshotBuilder caption(Option<String> caption)
1198         {
1199             this.caption = checkNotNull(caption);
1200             return this;
1201         }
1202     }
1203 
1204     public static class VendorBuilder extends UnsafeBuilderWithLinks<Vendor, VendorBuilder>
1205     {
1206         private String name;
1207         private Option<String> description = none();
1208         private String email;
1209         private Option<Address> address = none();
1210         private Option<String> phone = none();
1211         private Option<String> otherContactDetails = none();
1212         private Map<String, URI> externalLinks = new HashMap<String, URI>();
1213 
1214         private VendorBuilder()
1215         {
1216             links.put("alternate", DEFAULT_URI);
1217         }
1218         
1219         private VendorBuilder(Vendor from)
1220         {
1221             links.put(from.getLinks());
1222             name = from.getName();
1223             description = from.getDescription();
1224             address = from.getAddress();
1225             email = from.getEmail();
1226             phone = from.getPhone();
1227             otherContactDetails = from.getOtherContactDetails();
1228             for (VendorExternalLinkType type: VendorExternalLinkType.values())
1229             {
1230                 for (URI u: from.getExternalLinkUri(type))
1231                 {
1232                     externalLinks.put(type.getKey(), u);
1233                 }
1234             }
1235         }
1236         
1237         protected Vendor buildUnsafe()
1238         {
1239             Vendor ret = new Vendor();
1240             ret._links = links.build();
1241             ret._embedded = new Vendor.Embedded();
1242             ret._embedded.logo = none();
1243             ret.address = address;
1244             ret.description = description;
1245             ret.email = email;
1246             ret.name = name;
1247             ret.otherContactDetails = otherContactDetails;
1248             ret.phone = phone;
1249             ret.vendorLinks = ImmutableMap.copyOf(externalLinks);
1250             ret.verifiedStatus = none();
1251             return validateInstance(ret);
1252         }
1253         
1254         /**
1255          * @see Vendor#getLogo()
1256          */
1257         public VendorBuilder logo(Option<ImageId> logo)
1258         {
1259             links.put("logo", logo.map(resourceIdToUri()));
1260             return this;
1261         }
1262 
1263         /**
1264          * @see Vendor#getName()
1265          */
1266         public VendorBuilder name(String name)
1267         {
1268             this.name = checkNotNull(name);
1269             return this;
1270         }
1271 
1272         /**
1273          * @see Vendor#getDescription()
1274          */
1275         public VendorBuilder description(Option<String> description)
1276         {
1277             this.description = checkNotNull(description);
1278             return this;
1279         }
1280 
1281         /**
1282          * @see Vendor#getAddress()
1283          */
1284         public VendorBuilder address(Option<Address> address)
1285         {
1286             this.address = checkNotNull(address);
1287             return this;
1288         }
1289         
1290         /**
1291          * @see Vendor#getEmail()
1292          */
1293         public VendorBuilder email(String email)
1294         {
1295             this.email = checkNotNull(email);
1296             return this;
1297         }
1298         
1299         /**
1300          * @see Vendor#getPhone()
1301          */
1302         public VendorBuilder phone(Option<String> phone)
1303         {
1304             this.phone = checkNotNull(phone);
1305             return this;
1306         }
1307 
1308         /**
1309          * @see Vendor#getOtherContactDetails()
1310          */
1311         public VendorBuilder otherContactDetails(Option<String> otherContactDetails)
1312         {
1313             this.otherContactDetails = checkNotNull(otherContactDetails);
1314             return this;
1315         }
1316 
1317         /**
1318          * Sets one of the vendor-specified external links for the vendor.
1319          * @param type a {@link VendorExternalLinkType}
1320          * @param uri the optional link URI
1321          * @see Vendor#getExternalLinkUri(VendorExternalLinkType)
1322          */
1323         public VendorBuilder externalLinkUri(VendorExternalLinkType type, Option<URI> uri)
1324         {
1325             externalLinks.remove(type.getKey());
1326             for (URI u: uri)
1327             {
1328                 externalLinks.put(type.getKey(), u);
1329             }
1330             return this;
1331         }
1332     }
1333 
1334     private static <T> Option<ImmutableList<T>> copyOptionalList(Option<Iterable<T>> list)
1335     {
1336         for (Iterable<T> l: list)
1337         {
1338             return some(ImmutableList.copyOf(l));
1339         }
1340         return none();
1341     }
1342 }