View Javadoc

1   /*
2      Copyright 2007 Ramon Servadei
3   
4      Licensed under the Apache License, Version 2.0 (the "License");
5      you may not use this file except in compliance with the License.
6      You may obtain a copy of the License at
7   
8          http://www.apache.org/licenses/LICENSE-2.0
9   
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15   */
16  package fulmine.model.field.containerdefinition;
17  
18  import static fulmine.util.Utils.logException;
19  
20  import java.util.Collection;
21  import java.util.LinkedHashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import fulmine.Type;
27  import fulmine.context.IPermissionProfile;
28  import fulmine.model.container.AbstractDynamicContainer;
29  import fulmine.model.container.IContainer;
30  import fulmine.model.field.AbstractField;
31  import fulmine.model.field.IField;
32  import fulmine.protocol.specification.FieldReader;
33  import fulmine.protocol.specification.FieldWriter;
34  import fulmine.protocol.specification.FrameReader;
35  import fulmine.protocol.specification.FrameWriter;
36  import fulmine.protocol.wire.IWireIdentity;
37  import fulmine.protocol.wire.SWFWireIdentityRegistry;
38  import fulmine.protocol.wire.WireIdentity;
39  import fulmine.protocol.wire.operation.IOperationScope;
40  import fulmine.util.collection.CollectionFactory;
41  import fulmine.util.collection.CollectionUtils;
42  import fulmine.util.log.AsyncLog;
43  import fulmine.util.reference.is;
44  
45  /**
46   * Default implementation of an {@link IContainerDefinitionField}. The
47   * definition is made up of {@link DescriptorField} fields. These descriptor
48   * fields provide the necessary meta data for a transmitting container to write
49   * an {@link IField} using its wire identity and provides the receiving
50   * container with the information to construct the correct field type.
51   * <p>
52   * The fields of this implementation have SWF wire identities. There is no way
53   * to send the definition using IWF as, by design, the IWF code maps to a field
54   * and this is one of the functions the definition provides.
55   * <p>
56   * Static definitions are 'singletons'; all containers of the same type share
57   * the same static definition. Static definitions are not sent on-the-wire or
58   * mutated.
59   * 
60   * @author Ramon Servadei
61   * 
62   */
63  public final class ContainerDefinitionField extends AbstractField implements
64      IContainerDefinitionField
65  {
66  
67      private final static AsyncLog LOG =
68          new AsyncLog(ContainerDefinitionField.class);
69  
70      @Override
71      protected AsyncLog getLog()
72      {
73          return LOG;
74      }
75  
76      /** A special code to indicate a field is to be removed */
77      static final byte REMOVE_FIELD = -1;
78  
79      /**
80       * The reader task executed by the {@link FieldReader} when reading a frame
81       * containing a container definition. Only dynamic definitions are sent on
82       * the wire.
83       */
84      private final class ReaderTask implements FieldReader.IFieldReaderTask
85      {
86          public void read(IOperationScope scope, int fieldId, byte[] dataBuffer,
87              int dataStart, int dataLen)
88          {
89              throw new IllegalStateException(
90                  "reading IWF fields is invalid for a container definition");
91          }
92  
93          public void read(IOperationScope scope, String fieldId,
94              byte[] dataBuffer, int dataStart, int dataLen)
95          {
96              // create the descriptor field and then read its state
97              DescriptorField desc = new DescriptorField(fieldId);
98              desc.readState(scope, dataBuffer, dataStart, dataLen);
99              if (desc.getDataType() == REMOVE_FIELD)
100             {
101                 final IField field =
102                     ContainerDefinitionField.this.getContainer().get(fieldId);
103                 if (field != null)
104                 {
105                     ContainerDefinitionField.this.getContainer().remove(field);
106                 }
107                 // always remove the description, even if there was no field
108                 // added to the container
109                 remove(desc);
110             }
111             else
112             {
113                 /*
114                  * Fields are actually added in the AbstractContainer's
115                  * ReaderTask. This then triggers ContainerFieldAddedEvents with
116                  * a fully populated field.
117                  */
118                 add(desc);
119             }
120         }
121     }
122 
123     /**
124      * The counter for the IWF wire identity integer code (wire code). The
125      * container definition has a wire code that is
126      * {@link Type#CONTAINER_DEFINITION}.
127      */
128     // DO NOT CHANGE THIS!!!
129     int wireCodeCounter = Type.CONTAINER_DEFINITION.value() + 1;
130 
131     /**
132      * The fields making this definition, must be stored in the same order as
133      * added via {@link #add(IField)}.
134      */
135     private Map<String, DescriptorField> fields =
136         new LinkedHashMap<String, DescriptorField>(1);
137 
138     /** Map the IWF wire identity integer code to the {@link IField} */
139     private Map<Integer, DescriptorField> descriptorFields =
140         CollectionFactory.newMap(1);
141 
142     /** The changes to send */
143     private List<DescriptorField> changes = CollectionFactory.newList(1);
144 
145     /** Flag to indicate if the definition is static */
146     private boolean dynamic = true;
147 
148     /**
149      * Constructor for a static definition
150      * 
151      * @param definition
152      *            the unique name for the definition
153      */
154     public ContainerDefinitionField(String definition, IField... fields)
155     {
156         super(definition, Type.CONTAINER_DEFINITION,
157             IPermissionProfile.DEFAULT_APPLICATION,
158             IPermissionProfile.DEFAULT_PERMISSION);
159         setContainer(null);
160         for (IField field : fields)
161         {
162             try
163             {
164                 add(field);
165             }
166             catch (Exception e)
167             {
168                 logException(LOG, field, e);
169             }
170         }
171         this.dynamic = false;
172     }
173 
174     /**
175      * Constructor for a dynamic definition
176      * 
177      * @param container
178      *            a dynamic container (can have new fields added/removed)
179      */
180     public ContainerDefinitionField(AbstractDynamicContainer container)
181     {
182         super(container.getIdentity(), Type.CONTAINER_DEFINITION,
183             IPermissionProfile.DEFAULT_APPLICATION,
184             IPermissionProfile.DEFAULT_PERMISSION);
185         setContainer(container);
186     }
187 
188     public boolean isDynamic()
189     {
190         return this.dynamic;
191     }
192 
193     public void add(IField field)
194     {
195         if (!this.dynamic)
196         {
197             throw new IllegalStateException(
198                 "Cannot call add on a static container definition");
199         }
200         /*
201          * veto self, this happens during the construction of the
202          * AbstractDynamicContainer (it creates itself with a
203          * ContainerDefinition) or if the container is a remote reference (it
204          * will never write itself in that case)
205          */
206         if (this.equals(field)
207             || (getContainer() != null && !getContainer().isLocal()))
208         {
209             return;
210         }
211         /*
212          * For a static definition, add() is called during the ctor of the
213          * definition (its static). For dynamic definitions, this is called from
214          * the add() of the container
215          */
216         final DescriptorField desc = new DescriptorField(field.getIdentity());
217         desc.setWireCode(this.wireCodeCounter++);
218         desc.setDataType(field.getType().value());
219         desc.setPermission(field.getPermission());
220         desc.setApplication(field.getApplication());
221         add(desc);
222         getChanges().add(desc);
223         notifyChange();
224     }
225 
226     private void add(final DescriptorField desc)
227     {
228         getFields().put(desc.getIdentity(), desc);
229         getDescriptorFields().put(Integer.valueOf(desc.getWireCode()), desc);
230     }
231 
232     public void remove(IField field)
233     {
234         if (!this.dynamic)
235         {
236             throw new IllegalStateException(
237                 "Cannot call remove on a static container definition");
238         }
239         final DescriptorField remove = getFields().get(field.getIdentity());
240         if (remove != null)
241         {
242             remove.setDataType(REMOVE_FIELD);
243             getChanges().add(remove);
244             notifyChange();
245         }
246         else
247         {
248             if (getLog().isDebugEnabled())
249             {
250                 getLog().debug("No field descriptor for " + field.getIdentity());
251             }
252         }
253     }
254 
255     public String getIdentityFor(IWireIdentity wireId)
256     {
257         if (wireId.isIntegerWireFormat())
258         {
259             return getIdentityForWireCode(wireId.getAsInteger());
260         }
261         throw new IllegalArgumentException(
262             "Cannot work with an SWF wire identity");
263     }
264 
265     public IWireIdentity getWireIdentityFor(String identity)
266     {
267         return WireIdentity.get(getWireCodeForIdentity(identity));
268     }
269 
270     public String getIdentityForWireCode(int wireCode)
271     {
272         final IField field =
273             getDescriptorFields().get(Integer.valueOf(wireCode));
274         if (field != null)
275         {
276             return field.getIdentity();
277         }
278         throw new IllegalArgumentException("No field found for wire code "
279             + wireCode + ", codes are: "
280             + CollectionUtils.toFormattedString(getDescriptorFields()));
281     }
282 
283     public int getWireCodeForIdentity(String identity)
284     {
285         if (getIdentity().equals(identity))
286         {
287             return Type.CONTAINER_DEFINITION.value();
288         }
289         final IField field = getFields().get(identity);
290         if (field != null)
291         {
292             final int fieldWireCode = ((DescriptorField) field).getWireCode();
293             return fieldWireCode;
294         }
295         throw new IllegalArgumentException("No field found for " + identity);
296     }
297 
298     @Override
299     protected boolean doReadState(IOperationScope scope, byte[] buffer,
300         int start, int numberOfBytes) throws Exception
301     {
302         // the fields are read using a string wire format
303         FrameReader.readNestedSWF(scope, buffer, start, numberOfBytes,
304             this.new ReaderTask());
305         return true;
306     }
307 
308     @Override
309     protected boolean doWriteState(IOperationScope scope, IWireIdentity wireId,
310         byte[][] headerBuffer, int[] headerBufferPosition, byte[][] dataBuffer,
311         int[] dataBufferPosition, boolean completeState) throws Exception
312     {
313         final Collection<DescriptorField> fieldsToWrite =
314             (completeState ? getFields().values() : getChanges());
315         if (fieldsToWrite.size() == 0)
316         {
317             if (LOG.isDebugEnabled())
318             {
319                 LOG.debug("No changes to write for " + this);
320             }
321             return true;
322         }
323         if (LOG.isDebugEnabled())
324         {
325             LOG.debug("Writing " + fieldsToWrite);
326         }
327         final byte[] frame =
328             FrameWriter.writeNested(fieldsToWrite,
329                 new SWFWireIdentityRegistry(), scope, completeState);
330         FieldWriter.writeRawBytesField(wireId, frame, headerBuffer,
331             headerBufferPosition, dataBuffer, dataBufferPosition);
332         for (DescriptorField field : getChanges())
333         {
334             try
335             {
336                 if (field.getDataType() == REMOVE_FIELD)
337                 {
338                     remove(field);
339                 }
340             }
341             catch (Exception e)
342             {
343                 logException(LOG, field, e);
344             }
345         }
346         return true;
347     }
348 
349     private void remove(DescriptorField field)
350     {
351         try
352         {
353             getDescriptorFields().remove(
354                 Integer.valueOf(getWireCodeForIdentity(field.getIdentity())));
355         }
356         catch (Exception e)
357         {
358             logException(getLog(), "Did not find wire code for " + field
359                 + " to remove", e);
360         }
361         try
362         {
363             getFields().remove(field.getIdentity());
364         }
365         catch (Exception e)
366         {
367             logException(getLog(), "Did not find " + field + " to remove", e);
368         }
369     }
370 
371     public void populate(IContainer container)
372     {
373         final Collection<DescriptorField> values = getFields().values();
374         for (DescriptorField descriptorField : values)
375         {
376             try
377             {
378                 container.add(descriptorField.createField());
379             }
380             catch (Exception e)
381             {
382                 logException(LOG, descriptorField, e);
383             }
384         }
385     }
386 
387     /**
388      * Signal the container that this has changed.
389      */
390     private void notifyChange()
391     {
392         if (getContainer() != null)
393         {
394             getContainer().addEvent(this);
395         }
396     }
397 
398     public String[] getComponentIdentities()
399     {
400         final Set<String> keySet = getFields().keySet();
401         return keySet.toArray(new String[keySet.size()]);
402     }
403 
404     @Override
405     protected void doComponentDestroy()
406     {
407         super.doComponentDestroy();
408         // DO NOT CALL destroy() ON container
409         getChanges().clear();
410         getFields().clear();
411         getDescriptorFields().clear();
412     }
413 
414     public String getValueAsString()
415     {
416         return (this.dynamic ? "dynamic" : "static") + getFields().values();
417     }
418 
419     public void resetChanges()
420     {
421         getChanges().clear();
422     }
423 
424     public boolean containsDefinition(int wireCode)
425     {
426         return getDescriptorFields().containsKey(Integer.valueOf(wireCode));
427     }
428 
429     public IField createField(int wireCode)
430     {
431         return getDescriptorFields().get(Integer.valueOf(wireCode)).createField();
432     }
433 
434     public Object getValue()
435     {
436         return getValueAsString();
437     }
438 
439     public byte getApplication(int wireCode)
440     {
441         return getDescriptorFields().get(Integer.valueOf(wireCode)).getApplication();
442     }
443 
444     public short getPermission(int wireCode)
445     {
446         return getDescriptorFields().get(Integer.valueOf(wireCode)).getPermission();
447     }
448 
449     void setFields(Map<String, DescriptorField> fields)
450     {
451         this.fields = fields;
452     }
453 
454     Map<String, DescriptorField> getFields()
455     {
456         return this.fields;
457     }
458 
459     void setDescriptorFields(Map<Integer, DescriptorField> descriptorFields)
460     {
461         this.descriptorFields = descriptorFields;
462     }
463 
464     Map<Integer, DescriptorField> getDescriptorFields()
465     {
466         return this.descriptorFields;
467     }
468 
469     void setChanges(List<DescriptorField> changes)
470     {
471         this.changes = changes;
472     }
473 
474     List<DescriptorField> getChanges()
475     {
476         return this.changes;
477     }
478 
479     @Override
480     public boolean equals(Object obj)
481     {
482         if (is.same(this, obj))
483         {
484             return true;
485         }
486         if (is.differentClass(this, obj))
487         {
488             return false;
489         }
490         return super.equals(obj);
491         /*
492          * Don't bother testing the fields and wireCodes - removed ones really
493          * duff things up.
494          */
495     }
496 
497     @Override
498     public Object clone() throws CloneNotSupportedException
499     {
500         final ContainerDefinitionField clone =
501             (ContainerDefinitionField) super.clone();
502         // deep-copy
503         List<DescriptorField> changes = CollectionFactory.newList();
504         clone.setChanges(changes);
505         clone.getChanges().addAll(getChanges());
506         Map<String, DescriptorField> fields = CollectionFactory.newMap();
507         clone.setFields(fields);
508         clone.getFields().putAll(getFields());
509         Map<Integer, DescriptorField> descriptorFields =
510             CollectionFactory.newMap();
511         clone.setDescriptorFields(descriptorFields);
512         clone.getDescriptorFields().putAll(getDescriptorFields());
513         return clone;
514     }
515 
516 }