001 package org.bukkit.metadata;
002
003 import java.lang.ref.SoftReference;
004 import java.util.concurrent.Callable;
005
006 import org.apache.commons.lang.Validate;
007 import org.bukkit.plugin.Plugin;
008
009 /**
010 * The LazyMetadataValue class implements a type of metadata that is not
011 * computed until another plugin asks for it.
012 * <p>
013 * By making metadata values lazy, no computation is done by the providing
014 * plugin until absolutely necessary (if ever). Additionally,
015 * LazyMetadataValue objects cache their values internally unless overridden
016 * by a {@link CacheStrategy} or invalidated at the individual or plugin
017 * level. Once invalidated, the LazyMetadataValue will recompute its value
018 * when asked.
019 */
020 public class LazyMetadataValue extends MetadataValueAdapter implements MetadataValue {
021 private Callable<Object> lazyValue;
022 private CacheStrategy cacheStrategy;
023 private SoftReference<Object> internalValue;
024 private static final Object ACTUALLY_NULL = new Object();
025
026 /**
027 * Initialized a LazyMetadataValue object with the default
028 * CACHE_AFTER_FIRST_EVAL cache strategy.
029 *
030 * @param owningPlugin the {@link Plugin} that created this metadata
031 * value.
032 * @param lazyValue the lazy value assigned to this metadata value.
033 */
034 public LazyMetadataValue(Plugin owningPlugin, Callable<Object> lazyValue) {
035 this(owningPlugin, CacheStrategy.CACHE_AFTER_FIRST_EVAL, lazyValue);
036 }
037
038 /**
039 * Initializes a LazyMetadataValue object with a specific cache strategy.
040 *
041 * @param owningPlugin the {@link Plugin} that created this metadata
042 * value.
043 * @param cacheStrategy determines the rules for caching this metadata
044 * value.
045 * @param lazyValue the lazy value assigned to this metadata value.
046 */
047 public LazyMetadataValue(Plugin owningPlugin, CacheStrategy cacheStrategy, Callable<Object> lazyValue) {
048 super(owningPlugin);
049 Validate.notNull(cacheStrategy, "cacheStrategy cannot be null");
050 Validate.notNull(lazyValue, "lazyValue cannot be null");
051 this.internalValue = new SoftReference<Object>(null);
052 this.lazyValue = lazyValue;
053 this.cacheStrategy = cacheStrategy;
054 }
055
056 /**
057 * Protected special constructor used by FixedMetadataValue to bypass
058 * standard setup.
059 */
060 protected LazyMetadataValue(Plugin owningPlugin) {
061 super(owningPlugin);
062 }
063
064 public Object value() {
065 eval();
066 Object value = internalValue.get();
067 if (value == ACTUALLY_NULL) {
068 return null;
069 }
070 return value;
071 }
072
073 /**
074 * Lazily evaluates the value of this metadata item.
075 *
076 * @throws MetadataEvaluationException if computing the metadata value
077 * fails.
078 */
079 private synchronized void eval() throws MetadataEvaluationException {
080 if (cacheStrategy == CacheStrategy.NEVER_CACHE || internalValue.get() == null) {
081 try {
082 Object value = lazyValue.call();
083 if (value == null) {
084 value = ACTUALLY_NULL;
085 }
086 internalValue = new SoftReference<Object>(value);
087 } catch (Exception e) {
088 throw new MetadataEvaluationException(e);
089 }
090 }
091 }
092
093 public synchronized void invalidate() {
094 if (cacheStrategy != CacheStrategy.CACHE_ETERNALLY) {
095 internalValue.clear();
096 }
097 }
098
099 /**
100 * Describes possible caching strategies for metadata.
101 */
102 public enum CacheStrategy {
103 /**
104 * Once the metadata value has been evaluated, do not re-evaluate the
105 * value until it is manually invalidated.
106 */
107 CACHE_AFTER_FIRST_EVAL,
108
109 /**
110 * Re-evaluate the metadata item every time it is requested
111 */
112 NEVER_CACHE,
113
114 /**
115 * Once the metadata value has been evaluated, do not re-evaluate the
116 * value in spite of manual invalidation.
117 */
118 CACHE_ETERNALLY
119 }
120 }