View Javadoc

1   /*
2    * Entreri, an entity-component framework in Java
3    *
4    * Copyright (c) 2013, Michael Ludwig
5    * All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without modification,
8    * are permitted provided that the following conditions are met:
9    *
10   *     Redistributions of source code must retain the above copyright notice,
11   *         this list of conditions and the following disclaimer.
12   *     Redistributions in binary form must reproduce the above copyright notice,
13   *         this list of conditions and the following disclaimer in the
14   *         documentation and/or other materials provided with the distribution.
15   *
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
20   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  package com.lhkbob.entreri.property;
28  
29  import java.lang.reflect.Method;
30  import java.util.Arrays;
31  
32  /**
33   * ObjectProperty is an implementation of Property that stores the property data as a
34   * number of packed Object references for each property. Because it is not primitive data,
35   * cache locality will suffer compared to the primitive property types, but it will allow
36   * you to store arbitrary objects.
37   * <p/>
38   * However, ObjectProperty assumes that all component values can be null, and the default
39   * value is null. If this is not an acceptable contract then a custom property must be
40   * defined with a factory capable of constructing proper default instances.
41   *
42   * @author Michael Ludwig
43   */
44  @Factory(ObjectProperty.Factory.class)
45  public final class ObjectProperty implements Property {
46      private Object[] data;
47  
48      /**
49       * Create an ObjectProperty.
50       */
51      public ObjectProperty() {
52          data = new Object[1];
53      }
54  
55      /**
56       * Return a PropertyFactory that creates ObjectProperties using the given cloning
57       * policy.
58       *
59       * @return A PropertyFactory for ObjectProperty
60       */
61      public static PropertyFactory<ObjectProperty> factory(Clone.Policy policy) {
62          return new Factory(policy);
63      }
64  
65      /**
66       * Return the backing int array of this property's IndexedDataStore. The array may be
67       * longer than necessary for the number of components in the system. Data can be
68       * accessed for a component directly using the component's index.
69       *
70       * @return The Object data for all packed properties that this property has been
71       *         packed with
72       */
73      public Object[] getIndexedData() {
74          return data;
75      }
76  
77      /**
78       * Get the value stored in this property for the given component index.
79       *
80       * @param componentIndex The component's index
81       *
82       * @return The object at the given offset for the given component
83       *
84       * @throws ArrayIndexOutOfBoundsException if the componentIndex is invalid
85       */
86      @SuppressWarnings("unchecked")
87      public Object get(int componentIndex) {
88          return data[componentIndex];
89      }
90  
91      /**
92       * Store <var>val</var> in this property for the given component index.
93       *
94       * @param componentIndex The index of the component being modified
95       * @param val            The value to store, can be null
96       *
97       * @throws ArrayIndexOutOfBoundsException if the componentIndex is invalid
98       */
99      public void set(int componentIndex, Object val) {
100         data[componentIndex] = val;
101     }
102 
103     @Override
104     public void swap(int a, int b) {
105         Object t = data[a];
106         data[a] = data[b];
107         data[b] = t;
108     }
109 
110     @Override
111     public int getCapacity() {
112         return data.length;
113     }
114 
115     @Override
116     public void setCapacity(int size) {
117         data = Arrays.copyOf(data, size);
118     }
119 
120     /**
121      * Factory to create ObjectProperties.
122      *
123      * @author Michael Ludwig
124      */
125     @SuppressWarnings({ "rawtypes", "unchecked" })
126     public static class Factory implements PropertyFactory<ObjectProperty> {
127         private final Clone.Policy policy;
128 
129         public Factory(Attributes attrs) {
130             policy = attrs.hasAttribute(Clone.class) ? attrs.getAttribute(Clone.class)
131                                                             .value()
132                                                      : Clone.Policy.JAVA_DEFAULT;
133         }
134 
135         public Factory(Clone.Policy policy) {
136             this.policy = policy;
137         }
138 
139         @Override
140         public ObjectProperty create() {
141             return new ObjectProperty();
142         }
143 
144         @Override
145         public void setDefaultValue(ObjectProperty property, int index) {
146             property.set(index, null);
147         }
148 
149         @Override
150         public void clone(ObjectProperty src, int srcIndex, ObjectProperty dst,
151                           int dstIndex) {
152             switch (policy) {
153             case DISABLE:
154                 // assign default value
155                 setDefaultValue(dst, dstIndex);
156                 break;
157             case INVOKE_CLONE:
158                 Object orig = src.get(srcIndex);
159                 if (orig instanceof Cloneable) {
160                     try {
161                         // if they implemented Cloneable properly, clone() should
162                         // be public and take no arguments
163                         Method cloneMethod = orig.getClass().getMethod("clone");
164                         Object cloned = cloneMethod.invoke(orig);
165                         dst.set(dstIndex, cloned);
166                         break;
167                     } catch (Exception e) {
168                         // if they implement Cloneable, this shouldn't fail
169                         // and if it does it's not really our fault
170                         throw new RuntimeException(e);
171                     }
172                 }
173                 // else fall through to java default
174             case JAVA_DEFAULT:
175                 dst.set(dstIndex, src.get(srcIndex));
176                 break;
177             default:
178                 throw new UnsupportedOperationException(
179                         "Enum value not supported: " + policy);
180             }
181         }
182     }
183 }