001    package org.bukkit;
002    
003    import java.util.List;
004    import java.util.Map;
005    
006    import org.apache.commons.lang.Validate;
007    import org.bukkit.configuration.serialization.ConfigurationSerializable;
008    import org.bukkit.configuration.serialization.SerializableAs;
009    
010    import com.google.common.collect.ImmutableList;
011    import com.google.common.collect.ImmutableMap;
012    
013    /**
014     * Represents a single firework effect.
015     */
016    @SerializableAs("Firework")
017    public final class FireworkEffect implements ConfigurationSerializable {
018    
019        /**
020         * The type or shape of the effect.
021         */
022        public enum Type {
023            /**
024             * A small ball effect.
025             */
026            BALL,
027            /**
028             * A large ball effect.
029             */
030            BALL_LARGE,
031            /**
032             * A star-shaped effect.
033             */
034            STAR,
035            /**
036             * A burst effect.
037             */
038            BURST,
039            /**
040             * A creeper-face effect.
041             */
042            CREEPER,
043            ;
044        }
045    
046        /**
047         * Construct a firework effect.
048         *
049         * @return A utility object for building a firework effect
050         */
051        public static Builder builder() {
052            return new Builder();
053        }
054    
055        /**
056         * This is a builder for FireworkEffects.
057         *
058         * @see FireworkEffect#builder()
059         */
060        public static final class Builder {
061            boolean flicker = false;
062            boolean trail = false;
063            final ImmutableList.Builder<Color> colors = ImmutableList.builder();
064            ImmutableList.Builder<Color> fadeColors = null;
065            Type type = Type.BALL;
066    
067            Builder() {}
068    
069            /**
070             * Specify the type of the firework effect.
071             *
072             * @param type The effect type
073             * @return This object, for chaining
074             * @throws IllegalArgumentException If type is null
075             */
076            public Builder with(Type type) throws IllegalArgumentException {
077                Validate.notNull(type, "Cannot have null type");
078                this.type = type;
079                return this;
080            }
081    
082            /**
083             * Add a flicker to the firework effect.
084             *
085             * @return This object, for chaining
086             */
087            public Builder withFlicker() {
088                flicker = true;
089                return this;
090            }
091    
092            /**
093             * Set whether the firework effect should flicker.
094             *
095             * @param flicker true if it should flicker, false if not
096             * @return This object, for chaining
097             */
098            public Builder flicker(boolean flicker) {
099                this.flicker = flicker;
100                return this;
101            }
102    
103            /**
104             * Add a trail to the firework effect.
105             *
106             * @return This object, for chaining
107             */
108            public Builder withTrail() {
109                trail = true;
110                return this;
111            }
112    
113            /**
114             * Set whether the firework effect should have a trail.
115             *
116             * @param trail true if it should have a trail, false for no trail
117             * @return This object, for chaining
118             */
119            public Builder trail(boolean trail) {
120                this.trail = trail;
121                return this;
122            }
123    
124            /**
125             * Add a primary color to the firework effect.
126             *
127             * @param color The color to add
128             * @return This object, for chaining
129             * @throws IllegalArgumentException If color is null
130             */
131            public Builder withColor(Color color) throws IllegalArgumentException {
132                Validate.notNull(color, "Cannot have null color");
133    
134                colors.add(color);
135    
136                return this;
137            }
138    
139            /**
140             * Add several primary colors to the firework effect.
141             *
142             * @param colors The colors to add
143             * @return This object, for chaining
144             * @throws IllegalArgumentException If colors is null
145             * @throws IllegalArgumentException If any color is null (may be
146             *     thrown after changes have occurred)
147             */
148            public Builder withColor(Color...colors) throws IllegalArgumentException {
149                Validate.notNull(colors, "Cannot have null colors");
150                if (colors.length == 0) {
151                    return this;
152                }
153    
154                ImmutableList.Builder<Color> list = this.colors;
155                for (Color color : colors) {
156                    Validate.notNull(color, "Color cannot be null");
157                    list.add(color);
158                }
159    
160                return this;
161            }
162    
163            /**
164             * Add several primary colors to the firework effect.
165             *
166             * @param colors An iterable object whose iterator yields the desired
167             *     colors
168             * @return This object, for chaining
169             * @throws IllegalArgumentException If colors is null
170             * @throws IllegalArgumentException If any color is null (may be
171             *     thrown after changes have occurred)
172             */
173            public Builder withColor(Iterable<?> colors) throws IllegalArgumentException {
174                Validate.notNull(colors, "Cannot have null colors");
175    
176                ImmutableList.Builder<Color> list = this.colors;
177                for (Object color : colors) {
178                    if (!(color instanceof Color)) {
179                        throw new IllegalArgumentException(color + " is not a Color in " + colors);
180                    }
181                    list.add((Color) color);
182                }
183    
184                return this;
185            }
186    
187            /**
188             * Add a fade color to the firework effect.
189             *
190             * @param color The color to add
191             * @return This object, for chaining
192             * @throws IllegalArgumentException If colors is null
193             * @throws IllegalArgumentException If any color is null (may be
194             *     thrown after changes have occurred)
195             */
196            public Builder withFade(Color color) throws IllegalArgumentException {
197                Validate.notNull(color, "Cannot have null color");
198    
199                if (fadeColors == null) {
200                    fadeColors = ImmutableList.builder();
201                }
202    
203                fadeColors.add(color);
204    
205                return this;
206            }
207    
208            /**
209             * Add several fade colors to the firework effect.
210             *
211             * @param colors The colors to add
212             * @return This object, for chaining
213             * @throws IllegalArgumentException If colors is null
214             * @throws IllegalArgumentException If any color is null (may be
215             *     thrown after changes have occurred)
216             */
217            public Builder withFade(Color...colors) throws IllegalArgumentException {
218                Validate.notNull(colors, "Cannot have null colors");
219                if (colors.length == 0) {
220                    return this;
221                }
222    
223                ImmutableList.Builder<Color> list = this.fadeColors;
224                if (list == null) {
225                    list = this.fadeColors = ImmutableList.builder();
226                }
227    
228                for (Color color : colors) {
229                    Validate.notNull(color, "Color cannot be null");
230                    list.add(color);
231                }
232    
233                return this;
234            }
235    
236            /**
237             * Add several fade colors to the firework effect.
238             *
239             * @param colors An iterable object whose iterator yields the desired
240             *     colors
241             * @return This object, for chaining
242             * @throws IllegalArgumentException If colors is null
243             * @throws IllegalArgumentException If any color is null (may be
244             *     thrown after changes have occurred)
245             */
246            public Builder withFade(Iterable<?> colors) throws IllegalArgumentException {
247                Validate.notNull(colors, "Cannot have null colors");
248    
249                ImmutableList.Builder<Color> list = this.fadeColors;
250                if (list == null) {
251                    list = this.fadeColors = ImmutableList.builder();
252                }
253    
254                for (Object color : colors) {
255                    if (!(color instanceof Color)) {
256                        throw new IllegalArgumentException(color + " is not a Color in " + colors);
257                    }
258                    list.add((Color) color);
259                }
260    
261                return this;
262            }
263    
264            /**
265             * Create a {@link FireworkEffect} from the current contents of this
266             * builder.
267             * <p>
268             * To successfully build, you must have specified at least one color.
269             *
270             * @return The representative firework effect
271             */
272            public FireworkEffect build() {
273                return new FireworkEffect(
274                    flicker,
275                    trail,
276                    colors.build(),
277                    fadeColors == null ? ImmutableList.<Color>of() : fadeColors.build(),
278                    type
279                );
280            }
281        }
282    
283        private static final String FLICKER = "flicker";
284        private static final String TRAIL = "trail";
285        private static final String COLORS = "colors";
286        private static final String FADE_COLORS = "fade-colors";
287        private static final String TYPE = "type";
288    
289        private final boolean flicker;
290        private final boolean trail;
291        private final ImmutableList<Color> colors;
292        private final ImmutableList<Color> fadeColors;
293        private final Type type;
294        private String string = null;
295    
296        FireworkEffect(boolean flicker, boolean trail, ImmutableList<Color> colors, ImmutableList<Color> fadeColors, Type type) {
297            if (colors.isEmpty()) {
298                throw new IllegalStateException("Cannot make FireworkEffect without any color");
299            }
300            this.flicker = flicker;
301            this.trail = trail;
302            this.colors = colors;
303            this.fadeColors = fadeColors;
304            this.type = type;
305        }
306    
307        /**
308         * Get whether the firework effect flickers.
309         *
310         * @return true if it flickers, false if not
311         */
312        public boolean hasFlicker() {
313            return flicker;
314        }
315    
316        /**
317         * Get whether the firework effect has a trail.
318         *
319         * @return true if it has a trail, false if not
320         */
321        public boolean hasTrail() {
322            return trail;
323        }
324    
325        /**
326         * Get the primary colors of the firework effect.
327         *
328         * @return An immutable list of the primary colors
329         */
330        public List<Color> getColors() {
331            return colors;
332        }
333    
334        /**
335         * Get the fade colors of the firework effect.
336         *
337         * @return An immutable list of the fade colors
338         */
339        public List<Color> getFadeColors() {
340            return fadeColors;
341        }
342    
343        /**
344         * Get the type of the firework effect.
345         *
346         * @return The effect type
347         */
348        public Type getType() {
349            return type;
350        }
351    
352        /**
353         * @see ConfigurationSerializable
354         */
355        public static ConfigurationSerializable deserialize(Map<String, Object> map) {
356            Type type = Type.valueOf((String) map.get(TYPE));
357            if (type == null) {
358                throw new IllegalArgumentException(map.get(TYPE) + " is not a valid Type");
359            }
360    
361            return builder()
362                .flicker((Boolean) map.get(FLICKER))
363                .trail((Boolean) map.get(TRAIL))
364                .withColor((Iterable<?>) map.get(COLORS))
365                .withFade((Iterable<?>) map.get(FADE_COLORS))
366                .with(type)
367                .build();
368        }
369    
370        public Map<String, Object> serialize() {
371            return ImmutableMap.<String, Object>of(
372                FLICKER, flicker,
373                TRAIL, trail,
374                COLORS, colors,
375                FADE_COLORS, fadeColors,
376                TYPE, type.name()
377            );
378        }
379    
380        @Override
381        public String toString() {
382            final String string = this.string;
383            if (string == null) {
384                return this.string = "FireworkEffect:" + serialize();
385            }
386            return string;
387        }
388    
389        @Override
390        public int hashCode() {
391            /**
392             * TRUE and FALSE as per boolean.hashCode()
393             */
394            final int PRIME = 31, TRUE = 1231, FALSE = 1237;
395            int hash = 1;
396            hash = hash * PRIME + (flicker ? TRUE : FALSE);
397            hash = hash * PRIME + (trail ? TRUE : FALSE);
398            hash = hash * PRIME + type.hashCode();
399            hash = hash * PRIME + colors.hashCode();
400            hash = hash * PRIME + fadeColors.hashCode();
401            return hash;
402        }
403    
404        @Override
405        public boolean equals(Object obj) {
406            if (this == obj) {
407                return true;
408            }
409    
410            if (!(obj instanceof FireworkEffect)) {
411                return false;
412            }
413    
414            FireworkEffect that = (FireworkEffect) obj;
415            return this.flicker == that.flicker
416                    && this.trail == that.trail
417                    && this.type == that.type
418                    && this.colors.equals(that.colors)
419                    && this.fadeColors.equals(that.fadeColors);
420        }
421    }