KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > persist > evolve > Conversion


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: Conversion.java,v 1.9 2006/10/30 21:14:31 bostic Exp $
7  */

8
9 package com.sleepycat.persist.evolve;
10
11 import java.io.Serializable JavaDoc;
12
13 import com.sleepycat.persist.model.EntityModel;
14 import com.sleepycat.persist.raw.RawObject;
15 import com.sleepycat.persist.raw.RawType;
16
17 /**
18  * Converts an old version of an object value to conform to the current class
19  * or field definition.
20  *
21  * <p>The {@code Conversion} interface is implemented by the user. A
22  * {@code Conversion} instance is passed to the {@link Converter#Converter}
23  * constructor.</p>
24  *
25  * <p>The {@code Conversion} interface extends {@link Serializable} and the
26  * {@code Conversion} instance is serialized for storage using standard Java
27  * serialization. Normally, the {@code Conversion} class should only have
28  * transient fields that are initialized in the {@link #initialize} method.
29  * While non-transient fields are allowed, care must be taken to only include
30  * fields that are serializable and will not pull in large amounts of data.</p>
31  *
32  * <p>When a class conversion is specified, two special considerations
33  * apply:</p>
34  * <ol>
35  * <li>A class conversion is only applied when to instances of that class. The
36  * conversion will not be applied when the class when it appears as a
37  * superclass of the instance's class. In this case, a conversion for the
38  * instance's class must also be specified.</li>
39  * <li>Although field renaming (as well as all other changes) is handled by the
40  * conversion method, a field Renamer is still needed when a secondary key
41  * field is renamed and field Deleter is still needed when a secondary key
42  * field is deleted. This is necessary for evolution of the metadata;
43  * specifically, if the key name changes the database must be renamed and if
44  * the key field is deleted the secondary database must be deleted.</li>
45  * </ol>
46  *
47  * <p>The {@code Conversion} class must implement the standard equals method.
48  * See {@link #equals} for more information.</p>
49  *
50  * <p>Conversions of simple types are generally simple. For example, a {@code
51  * String} field that contains only integer values can be easily converted to
52  * an {@code int} field:</p>
53  * <pre class="code">
54  * // The old class. Version 0 is implied.
55  * //
56  * {@literal @Persistent}
57  * class Address {
58  * String zipCode;
59  * ...
60  * }
61  *
62  * // The new class. A new version number must be assigned.
63  * //
64  * {@literal @Persistent(version=1)}
65  * class Address {
66  * int zipCode;
67  * ...
68  * }
69  *
70  * // The conversion class.
71  * //
72  * class MyConversion1 implements Conversion {
73  *
74  * public void initialize(EntityModel model) {
75  * // No initialization needed.
76  * }
77  *
78  * public Object convert(Object fromValue) {
79  * return Integer.valueOf((String) fromValue);
80  * }
81  *
82  * {@code @Override}
83  * public boolean equals(Object o) {
84  * return o instanceof MyConversion1;
85  * }
86  * }
87  *
88  * // Create a field converter mutation.
89  * //
90  * Converter converter = new Converter(Address.class.getName(), 0,
91  * "zipCode", new MyConversion1());
92  *
93  * // Configure the converter as described {@link Mutations here}.</pre>
94  *
95  * <p>A conversion may perform arbitrary transformations on an object. For
96  * example, a conversion may transform a single String address field into an
97  * Address object containing four fields for street, city, state and zip
98  * code.</p>
99  * <pre class="code">
100  * // The old class. Version 0 is implied.
101  * //
102  * {@literal @Entity}
103  * class Person {
104  * String address;
105  * ...
106  * }
107  *
108  * // The new class. A new version number must be assigned.
109  * //
110  * {@literal @Entity(version=1)}
111  * class Person {
112  * Address address;
113  * ...
114  * }
115  *
116  * // The new address class.
117  * //
118  * {@literal @Persistent}
119  * class Address {
120  * String street;
121  * String city;
122  * String state;
123  * int zipCode;
124  * ...
125  * }
126  *
127  * class MyConversion2 implements Conversion {
128  * private transient RawType addressType;
129  *
130  * public void initialize(EntityModel model) {
131  * addressType = model.getRawType(Address.class.getName());
132  * }
133  *
134  * public Object convert(Object fromValue) {
135  *
136  * // Parse the old address and populate the new address fields
137  * //
138  * String oldAddress = (String) fromValue;
139  * {@literal Map<String,Object> addressValues = new HashMap<String,Object>();}
140  * addressValues.put("street", parseStreet(oldAddress));
141  * addressValues.put("city", parseCity(oldAddress));
142  * addressValues.put("state", parseState(oldAddress));
143  * addressValues.put("zipCode", parseZipCode(oldAddress));
144  *
145  * // Return new raw Address object
146  * //
147  * return new RawObject(addressType, addressValues, null);
148  * }
149  *
150  * {@code @Override}
151  * public boolean equals(Object o) {
152  * return o instanceof MyConversion2;
153  * }
154  *
155  * private String parseStreet(String oldAddress) { ... }
156  * private String parseCity(String oldAddress) { ... }
157  * private String parseState(String oldAddress) { ... }
158  * private Integer parseZipCode(String oldAddress) { ... }
159  * }
160  *
161  * // Create a field converter mutation.
162  * //
163  * Converter converter = new Converter(Person.class.getName(), 0,
164  * "address", new MyConversion2());
165  *
166  * // Configure the converter as described {@link Mutations here}.</pre>
167  *
168  * <p>Note that when a conversion returns a {@link RawObject}, it must return
169  * it with a {@link RawType} that is current as defined by the current class
170  * definitions. The proper types can be obtained from the {@link EntityModel}
171  * in the conversion's {@link #initialize initialize} method.</p>
172  *
173  * <p>A variation on the example above is where several fields in a class
174  * (street, city, state and zipCode) are converted to a single field (address).
175  * In this case a class converter rather than a field converter is used.</p>
176  *
177  * <pre class="code">
178  * // The old class. Version 0 is implied.
179  * //
180  * {@literal @Entity}
181  * class Person {
182  * String street;
183  * String city;
184  * String state;
185  * int zipCode;
186  * ...
187  * }
188  *
189  * // The new class. A new version number must be assigned.
190  * //
191  * {@literal @Entity(version=1)}
192  * class Person {
193  * Address address;
194  * ...
195  * }
196  *
197  * // The new address class.
198  * //
199  * {@literal @Persistent}
200  * class Address {
201  * String street;
202  * String city;
203  * String state;
204  * int zipCode;
205  * ...
206  * }
207  *
208  * class MyConversion3 implements Conversion {
209  * private transient RawType newPersonType;
210  * private transient RawType addressType;
211  *
212  * public void initialize(EntityModel model) {
213  * newPersonType = model.getRawType(Person.class.getName());
214  * addressType = model.getRawType(Address.class.getName());
215  * }
216  *
217  * public Object convert(Object fromValue) {
218  *
219  * // Get field value maps for old and new objects.
220  * //
221  * RawObject person = (RawObject) fromValue;
222  * {@literal Map<String,Object> personValues = person.getValues();}
223  * {@literal Map<String,Object> addressValues = new HashMap<String,Object>();}
224  * RawObject address = new RawObject(addressType, addressValues, null);
225  *
226  * // Remove the old address fields and insert the new one.
227  * //
228  * addressValues.put("street", personValues.remove("street"));
229  * addressValues.put("city", personValues.remove("city"));
230  * addressValues.put("state", personValues.remove("state"));
231  * addressValues.put("zipCode", personValues.remove("zipCode"));
232  * personValues.put("address", address);
233  *
234  * return new RawObject(newPersonType, personValues, person.getSuper());
235  * }
236  *
237  * {@code @Override}
238  * public boolean equals(Object o) {
239  * return o instanceof MyConversion3;
240  * }
241  * }
242  *
243  * // Create a class converter mutation.
244  * //
245  * Converter converter = new Converter(Person.class.getName(), 0,
246  * new MyConversion3());
247  *
248  * // Configure the converter as described {@link Mutations here}.</pre>
249  *
250  *
251  * <p>A conversion can also handle changes to class hierarchies. For example,
252  * if a "name" field originally declared in class A is moved to its superclass
253  * B, a conversion can move the field value accordingly:</p>
254  *
255  * <pre class="code">
256  * // The old classes. Version 0 is implied.
257  * //
258  * {@literal @Persistent}
259  * class A extends B {
260  * String name;
261  * ...
262  * }
263  * {@literal @Persistent}
264  * abstract class B {
265  * ...
266  * }
267  *
268  * // The new classes. A new version number must be assigned.
269  * //
270  * {@literal @Persistent(version=1)}
271  * class A extends B {
272  * ...
273  * }
274  * {@literal @Persistent(version=1)}
275  * abstract class B {
276  * String name;
277  * ...
278  * }
279  *
280  * class MyConversion4 implements Conversion {
281  * private transient RawType newAType;
282  * private transient RawType newBType;
283  *
284  * public void initialize(EntityModel model) {
285  * newAType = model.getRawType(A.class.getName());
286  * newBType = model.getRawType(B.class.getName());
287  * }
288  *
289  * public Object convert(Object fromValue) {
290  * RawObject oldA = (RawObject) fromValue;
291  * RawObject oldB = oldA.getSuper();
292  * {@literal Map<String,Object> aValues = oldA.getValues();}
293  * {@literal Map<String,Object> bValues = oldB.getValues();}
294  * bValues.put("name", aValues.remove("name"));
295  * RawObject newB = new RawObject(newBType, bValues, oldB.getSuper());
296  * RawObject newA = new RawObject(newAType, aValues, newB);
297  * return newA;
298  * }
299  *
300  * {@code @Override}
301  * public boolean equals(Object o) {
302  * return o instanceof MyConversion4;
303  * }
304  * }
305  *
306  * // Create a class converter mutation.
307  * //
308  * Converter converter = new Converter(A.class.getName(), 0,
309  * new MyConversion4());
310  *
311  * // Configure the converter as described {@link Mutations here}.</pre>
312  *
313  * <p>A conversion may return an instance of a different class entirely, as
314  * long as it conforms to current class definitions and is the type expected
315  * in the given context (a subtype of the old type, or a type compatible with
316  * the new field type). For example, a field that is used to discriminate
317  * between two types of objects could be removed and replaced by two new
318  * subclasses:</p> <pre class="code">
319  * // The old class. Version 0 is implied.
320  * //
321  * {@literal @Persistent}
322  * class Pet {
323  * boolean isCatNotDog;
324  * ...
325  * }
326  *
327  * // The new classes. A new version number must be assigned to the Pet class.
328  * //
329  * {@literal @Persistent(version=1)}
330  * class Pet {
331  * ...
332  * }
333  * {@literal @Persistent}
334  * class Cat extends Pet {
335  * ...
336  * }
337  * {@literal @Persistent}
338  * class Dog extends Pet {
339  * ...
340  * }
341  *
342  * class MyConversion5 implements Conversion {
343  * private transient RawType newPetType;
344  * private transient RawType dogType;
345  * private transient RawType catType;
346  *
347  * public void initialize(EntityModel model) {
348  * newPetType = model.getRawType(Pet.class.getName());
349  * dogType = model.getRawType(Dog.class.getName());
350  * catType = model.getRawType(Cat.class.getName());
351  * }
352  *
353  * public Object convert(Object fromValue) {
354  * RawObject pet = (RawObject) fromValue;
355  * {@literal Map<String,Object> petValues = pet.getValues();}
356  * Boolean isCat = (Boolean) petValues.remove("isCatNotDog");
357  * RawObject newPet = new RawObject(newPetType, petValues,
358  * pet.getSuper());
359  * RawType newSubType = isCat ? catType : dogType;
360  * return new RawObject(newSubType, Collections.emptyMap(), newPet);
361  * }
362  *
363  * {@code @Override}
364  * public boolean equals(Object o) {
365  * return o instanceof MyConversion5;
366  * }
367  * }
368  *
369  * // Create a class converter mutation.
370  * //
371  * Converter converter = new Converter(Pet.class.getName(), 0,
372  * new MyConversion5());
373  *
374  * // Configure the converter as described {@link Mutations here}.</pre>
375  *
376  * <p>The primary limitation of a conversion is that it may access at most a
377  * single entity instance at one time. Conversions involving multiple entities
378  * at once may be made by performing a <a
379  * HREF="package-summary.html#storeConversion">store conversion</a>.</p>
380  *
381  * @see com.sleepycat.persist.evolve Class Evolution
382  * @author Mark Hayes
383  */

384 public interface Conversion extends Serializable JavaDoc {
385
386     /**
387      * Initializes the conversion, allowing it to obtain raw type information
388      * from the entity model.
389      */

390     void initialize(EntityModel model);
391
392     /**
393      * Converts an old version of an object value to conform to the current
394      * class or field definition.
395      *
396      * <p>If a {@link RuntimeException} is thrown by this method, it will be
397      * thrown to the original caller. Similarly, a {@link
398      * IllegalArgumentException} will be thrown to the original caller if the
399      * object returned by this method does not conform to current class
400      * definitions.</p>
401      *
402      * <p>The class of the input and output object may be one of the simple
403      * types or {@link RawObject}. For primitive types, the primitive wrapper
404      * class is used.</p>
405      *
406      * @param fromValue the object value being converted. The type of this
407      * value is defined by the old class version that is being converted.
408      *
409      * @return the converted object. The type of this value must conform to
410      * a current class definition. If this is a class conversion, it must
411      * be the current version of the class. If this is a field conversion, it
412      * must be of a type compatible with the current declared type of the
413      * field.
414      */

415     Object JavaDoc convert(Object JavaDoc fromValue);
416
417     /**
418      * The standard {@code equals} method that must be implemented by
419      * conversion class.
420      *
421      * <p>When mutations are specified when opening a store, the specified and
422      * previously stored mutations are compared for equality. If they are
423      * equal, there is no need to replace the existing mutations in the stored
424      * catalog. To accurately determine equality, the conversion class must
425      * implement the {@code equals} method.</p>
426      *
427      * <p>If the {@code equals} method is not explicitly implemented by the
428      * conversion class or a superclass other than {@code Object}, {@code
429      * IllegalArgumentException} will be thrown when the store is opened.</p>
430      *
431      * <p>Normally whenever {@code equals} is implemented the {@code hashCode}
432      * method should also be implemented to support hash sets and maps.
433      * However, hash sets and maps containing <code>Conversion</code> objects
434      * are not used by the DPL and therefore the DPL does not require
435      * {@code hashCode} to be implemented.</p>
436      */

437     boolean equals(Object JavaDoc other);
438 }
439
Popular Tags