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 }