package se.itu.systemet.domain; import java.util.Comparator; /** *

Represents a Product in a ProductLine.

* *

The data of a Product originally comes from the Systembolaget's * open data with their product line. An example product is shown below: *

 *  <artikel>
 *   <nr>152115</nr>
 *   <Artikelid>1087400</Artikelid>
 *   <Varnummer>1521</Varnummer>
 *   <Namn>Williams Caesar Augustus</Namn>
 *   <Namn2>Lager IPA Hybrid</Namn2>
 *   <Prisinklmoms>16.90</Prisinklmoms>
 *   <Pant>1.00</Pant>
 *   <Volymiml>330.00</Volymiml>
 *   <PrisPerLiter>51.21</PrisPerLiter>
 *   <Saljstart>2016-09-01</Saljstart>
 *   <Utgått>0</Utgått>
 *   <Varugrupp>Öl</Varugrupp>
 *    <Typ>Ljus lager</Typ>
 *   <Stil>Modern stil</Stil>
 *   <Forpackning>Burk</Forpackning>
 *   <Forslutning/>
 *   <Ursprung/>
 *   <Ursprunglandnamn>Storbritannien</Ursprunglandnamn>
 *   <Producent>Williams Brothers</Producent>
 *   <Leverantor>Brill & Co AB</Leverantor>
 *   <Argang/>
 *   <Provadargang/>
 *   <Alkoholhalt>4.00%</Alkoholhalt>
 *   <Sortiment>BS</Sortiment>
 *   <SortimentText>Övrigt sortiment</SortimentText>
 *   <Ekologisk>0</Ekologisk>
 *   <Etiskt>0</Etiskt>
 *   <Koscher>0</Koscher>
 *   <RavarorBeskrivning>Kornmalt samt humle av sorterna savinjski goldings, calypso och citra.</RavarorBeskrivning>
 *  </artikel>
 * 
*

*

* This class uses a builder for creation of its instances. The builder is the nested class Product.Builder. * A typical usage is shown below: *

 *     Product p1 = new Product.Builder()
 *       .name("Renat")
 *       .price(209.00)
 *       .alcohol(37.50)
 *       .volume(700)
 *       .nr(101)
 *       .productGroup("Okryddad sprit")
 *       .build();
 *     // Do something with p1...
 *
* Note that typically, Products aren't hardcoded like above. Typically, they rather are built using external data, * e.g. from a database or some web API call, etc. *

*/ public class Product { private String name; private double price; // SEK private double alcohol; // % alcohol by volume private int volume; // milliliters private int nr; // XML: nnn unique nr in the catalog private String productGroup; // e.g. Okryddad sprit private String type; // e.g. Syrlig öl /** * Defines the interface for objects we can export a * Product instance to. * * Hides the implementation, format and use from this class. */ public interface Exporter { void addName(String name); void addPrice(double price); void addAlcohol(double alcohol); void addVolume(int volume); void addNr(int nr); void addProductGroup(String productGroup); void addType(String type); } /** * Defines a Builder for an Object. */ public static class Builder { private String name; private double price; // SEK private double alcohol; // % alcohol by volume private int volume; // milliliters private int nr; // XML: nnn unique nr in the catalog private String productGroup; // e.g. Okryddad sprit private String type; /** * Provides the Product name * @param name The name for the Product to build * @return this Builder so that you can continue building */ public Builder name(String name) { this.name = name; return this; } /** * */ public Builder price(double price) { this.price = price; return this; } /** * */ public Builder alcohol(double alcohol) { this.alcohol = alcohol; return this; } /** * */ public Builder volume(int volume) { this.volume = volume; return this; } /** * */ public Builder nr(int nr) { this.nr = nr; return this; } /** * */ public Builder productGroup(String productGroup) { this.productGroup = productGroup; return this; } /** * */ public Builder type(String type) { this.type = type; return this; } /** * */ public Product build() { return new Product(this); } } /** * Constructs a Product using a Product.Builder * * @param builder The builder to use for building this Product */ public Product(Builder builder) { this.name = builder.name; this.price = builder.price; this.alcohol = builder.alcohol; this.volume = builder.volume; this.nr = builder.nr; this.productGroup = builder.productGroup; this.type = builder.type; } /** * This methods allows a Product to export itself to * an Exporter object, which could be used to create * various representations or other uses of this Product, * like for instance, a CSV row, an XML element, an * SQL insert statement, a PreparedStatement, a GUI component * or whatever. * * This class remains totally decoupled from the creation * of such a representation or object, since we limit ourselves * to Exporting to an Exporter object - and Exporter is an * interface owned by this class (as a nested static interface). * * Compare this method to the toString() method - this method * allows us to transfer the state of this object to some other * object. * * See: http://www.javaworld.com/article/2072302/core-java/more-on-getters-and-setters.html * by Allen Holub, for reference and the source of this idea. * * @param builder The Exporter to export ourselves to */ public void export(Exporter builder) { builder.addName(name); builder.addPrice(price); builder.addAlcohol(alcohol); builder.addNr(nr); builder.addVolume(volume); builder.addProductGroup(productGroup); builder.addType(type); } /** * A Comparator that orders Products based on their name. */ public static final Comparator NAME_ORDER = Comparator.comparing(Product::name); /** * A Comparator that orders Products based on their name, ignoring case. */ public static final Comparator NAME_CASE_INSENSITIVE_ORDER = (p1, p2) -> p1.name().toLowerCase().compareTo(p2.name().toLowerCase()); /** * A Comparator that orders Products based on their price. */ public static final Comparator PRICE_ORDER = Comparator.comparing(Product::price); /** * A Comparator that orders Products based on their alcohol level. */ public static final Comparator ALCOHOL_ORDER = Comparator.comparing(Product::alcohol); /** * A Comparator that orders Products based on their volume. */ public static final Comparator VOLUME_ORDER = Comparator.comparing(Product::volume); /** * Constructs a new Product. * @param name The name of this Product * @param alcohol The alcohol level (in percent alcohol by weight) of this Product * @param price The price of this Product (in SEK) * @param volume The volume (in millilitres) of this product */ public Product(String name, double alcohol, double price, int volume) { this.name = name; this.alcohol = alcohol; this.price = price; this.volume = volume; } /** * Returns the name of this Product * @return The name of this Product */ public String name() { return name; } /** * Returns the alcohol level of this Product * @return The alcohol level of this Product */ public double alcohol() { return alcohol; } /** * Returns the price of this Product * @return The price of this Product */ public double price() { return price; } /** * Returns the volume of this Product * @return The volume of this Product */ public int volume() { return volume; } /** * Returns the product group of this Product * @return The product group of this Product */ public String productGroup() { return productGroup; } /** * Returns the product type of this Product * @return The product type of this Product */ public String type() { return type; } /** * Returns the product number of this Product * @return The product number of this Product */ public int nr() { return nr; } /** * Returns this Product as a String on the format: *
   *NAME, PERCENT_ALCOHOL%, VOLUME ml, PRICE SEK
   *
* For instance: *
   *Renat, 37.50%, 700 ml, 209.00 SEK
   *
* @return this Product as a String */ @Override public String toString() { String type = this.type == null ? "" : " (" + this.type + "),"; return name + ", " + String.format("%.2f", alcohol) + "%, " + volume + " ml" + ", " + String.format("%.2f", price) + " SEK " + productGroup + type + ", Product number: " + nr; } /** * Uses name, alcohol, volume, price and nr * when deciding if this Product is equal to * another Product. * * Note: We could have settled with nr, since * it is the identity of a Product from the Systembolaget * source data (the XML file etc). */ @Override public boolean equals(Object other) { if (other == null) { return false; } if (! (other instanceof Product)) { return false; } Product that = (Product)other; return this.name.equals(that.name) && this.alcohol == that.alcohol && this.volume == that.volume && this.price == that.price && this.nr == that.nr; } @Override public int hashCode() { // Using hash algorithm from // Joshua Bloch - Effective Java - on hashcodes int code = 17; code = 31 * code + name.hashCode(); long alc = Double.doubleToLongBits(alcohol); code = 31 * code + (int) (alc ^ (alc >>> 32)); code = 31 * code + volume; long pri = Double.doubleToLongBits(price); code = 31 * code + (int) (pri ^ (pri >>> 32)); code = 31 * code + nr; return code; } }