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.impl;
28  
29  import com.lhkbob.entreri.*;
30  import com.lhkbob.entreri.property.Property;
31  
32  import java.lang.reflect.InvocationTargetException;
33  import java.lang.reflect.Method;
34  
35  /**
36   * AbstractComponent is the base class used for all generated proxy implementations of
37   * component subtypes. It provides an implementation for all of the declared methods in
38   * Component as well as equals() and hashCode(). It should not be subclassed or extended
39   * directly, but is used as the parent class of generated proxies. As specified in {@link
40   * com.lhkbob.entreri.Component}, all component type definitions are sub-interfaces of
41   * Component.
42   *
43   * @param <T> The type of component the AbstractComponent is safely cast-able to
44   */
45  public abstract class AbstractComponent<T extends Component> implements Component {
46      /**
47       * The ComponentRepository managing this component and all of its property data
48       */
49      protected final ComponentRepository<T> owner;
50  
51      /**
52       * The current index of the component. Subclasses must not modify directly, but should
53       * call setIndex(). This is provided to avoid the virtual getIndex() call.
54       */
55      protected int index;
56      private int id;
57  
58      /**
59       * Create a new component stored in the given ComponentRepository.
60       *
61       * @param owner The ComponentRepository owner
62       */
63      public AbstractComponent(ComponentRepository<T> owner) {
64          this.owner = owner;
65          this.index = 0;
66      }
67  
68      /**
69       * Set the index that defines the identity of this component.
70       *
71       * @param index The new index
72       */
73      void setIndex(int index) {
74          this.index = index;
75          this.id = owner.getId(index);
76      }
77  
78      @Override
79      public boolean isAlive() {
80          // we have to check the index of the Component because the ComponentRepository
81          // does not make sure the data's indices stay within bounds of the repository arrays
82          return index != 0 && index < owner.getMaxComponentIndex() &&
83                 owner.getId(index) == id;
84      }
85  
86      @Override
87      public Entity getEntity() {
88          // if isAlive() returns false, then the entity index will also be 0,
89          // so getEntityByIndex() returns null, which is expected
90          int entityIndex = owner.getEntityIndex(index);
91          return owner.getEntitySystem().getEntityByIndex(entityIndex);
92      }
93  
94      @Override
95      public EntitySystem getEntitySystem() {
96          return owner.getEntitySystem();
97      }
98  
99      @Override
100     public final int getVersion() {
101         return owner.getVersion(index);
102     }
103 
104     @Override
105     public final void updateVersion() {
106         if (isAlive()) {
107             owner.incrementVersion(index);
108         }
109     }
110 
111     @Override
112     public int getIndex() {
113         return index;
114     }
115 
116     @Override
117     public Owner notifyOwnershipGranted(Ownable obj) {
118         owner.getOwnerDelegate(index).notifyOwnershipGranted(obj);
119         // make sure to return the canonical component at the current index
120         return owner.getComponent(index);
121     }
122 
123     @Override
124     public void notifyOwnershipRevoked(Ownable obj) {
125         owner.getOwnerDelegate(index).notifyOwnershipRevoked(obj);
126     }
127 
128     @Override
129     public void setOwner(Owner owner) {
130         this.owner.getOwnerDelegate(index).setOwner(owner);
131     }
132 
133     @Override
134     public Owner getOwner() {
135         return owner.getOwnerDelegate(index).getOwner();
136     }
137 
138     @Override
139     public Class<T> getType() {
140         return owner.getType();
141     }
142 
143     @Override
144     public boolean isFlyweight() {
145         return !isAlive() || owner.getComponent(index) != this;
146     }
147 
148     @Override
149     public int hashCode() {
150         if (isAlive()) {
151             return (getType().hashCode() + 17 * (getEntity().getId() + 31));
152         } else {
153             return 0;
154         }
155     }
156 
157     @Override
158     public boolean equals(Object o) {
159         if (!(o instanceof AbstractComponent)) {
160             return false;
161         }
162         AbstractComponent<?> a = (AbstractComponent<?>) o;
163         return a.owner == owner && a.index == index;
164     }
165 
166     @Override
167     public String toString() {
168         if (index == 0) {
169             return "dead " + getType().getSimpleName();
170         } else {
171             StringBuilder sb = new StringBuilder(getType().getSimpleName());
172             sb.append("(").append(getEntity().getId());
173 
174             for (int i = 0; i < owner.getDeclaredPropertyCount(); i++) {
175                 sb.append(", ").append(owner.getDeclaredPropertyName(i)).append("=")
176                   .append(inspectProperty(owner.getProperty(i)));
177             }
178 
179             sb.append(")");
180             return sb.toString();
181         }
182     }
183 
184     private String inspectProperty(Property p) {
185         try {
186             Method get = p.getClass().getMethod("get", int.class);
187             Object v = get.invoke(p, index);
188 
189             if (v != null) {
190                 // strip out newlines
191                 return v.toString().replaceAll("[\n\r]", "");
192             } else {
193                 return "null";
194             }
195         } catch (NoSuchMethodException e) {
196             // should never happen
197             return "missing get() method";
198         } catch (InvocationTargetException | IllegalAccessException e) {
199             return "unable to inspect";
200         }
201     }
202 }