1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: EnumFormat.java,v 1.1 2008/02/07 17:12:27 mark Exp $
7 */
8
9package com.sleepycat.persist.impl;
10
11import java.lang.reflect.Array;
12import java.util.Arrays;
13import java.util.HashSet;
14import java.util.IdentityHashMap;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import com.sleepycat.persist.raw.RawObject;
20
21/**
22 * Format for all enum types.
23 *
24 * In this class we resort to using reflection to allocate arrays of enums.
25 * If there is a need for it, reflection could be avoided in the future by
26 * generating code as new array formats are encountered.
27 *
28 * @author Mark Hayes
29 */
30public class EnumFormat extends Format {
31
32    private static final long serialVersionUID = 1069833955604373538L;
33
34    private String[] names;
35    private transient Object[] values;
36
37    EnumFormat(Class type) {
38        super(type);
39        values = type.getEnumConstants();
40        names = new String[values.length];
41        for (int i = 0; i < names.length; i += 1) {
42            names[i] = ((Enum) values[i]).name();
43        }
44    }
45
46    @Override
47    public boolean isEnum() {
48        return true;
49    }
50
51    @Override
52    public List<String> getEnumConstants() {
53        return Arrays.asList(names);
54    }
55
56    @Override
57    void collectRelatedFormats(Catalog catalog,
58                               Map<String,Format> newFormats) {
59    }
60
61    @Override
62    void initialize(Catalog catalog, int initVersion) {
63        if (values == null) {
64            Class cls = getType();
65            if (cls != null) {
66                values = new Object[names.length];
67                for (int i = 0; i < names.length; i += 1) {
68                    values[i] = Enum.valueOf(cls, names[i]);
69                }
70            }
71        }
72    }
73
74    @Override
75    Object newArray(int len) {
76        return Array.newInstance(getType(), len);
77    }
78
79    @Override
80    public Object newInstance(EntityInput input, boolean rawAccess) {
81        int index = input.readEnumConstant(names);
82        if (rawAccess) {
83            return new RawObject(this, names[index]);
84        } else {
85            return values[index];
86        }
87    }
88
89    @Override
90    public Object readObject(Object o, EntityInput input, boolean rawAccess) {
91        /* newInstance reads the value -- do nothing here. */
92        return o;
93    }
94
95    @Override
96    void writeObject(Object o, EntityOutput output, boolean rawAccess) {
97        if (rawAccess) {
98            String name = ((RawObject) o).getEnum();
99            for (int i = 0; i < names.length; i += 1) {
100                if (names[i].equals(name)) {
101                    output.writeEnumConstant(names, i);
102                    return;
103                }
104            }
105        } else {
106            for (int i = 0; i < values.length; i += 1) {
107                if (o == values[i]) {
108                    output.writeEnumConstant(names, i);
109                    return;
110                }
111            }
112        }
113        throw new IllegalStateException("Bad enum: " + o);
114    }
115
116    @Override
117    Object convertRawObject(Catalog catalog,
118                            boolean rawAccess,
119                            RawObject rawObject,
120                            IdentityHashMap converted) {
121        String name = rawObject.getEnum();
122        for (int i = 0; i < names.length; i += 1) {
123            if (names[i].equals(name)) {
124                Object o = values[i];
125                converted.put(rawObject, o);
126                return o;
127            }
128        }
129        throw new IllegalArgumentException
130            ("Enum constant is not defined: " + name);
131    }
132
133    @Override
134    void skipContents(RecordInput input) {
135        input.skipFast(input.getPackedIntByteLength());
136    }
137
138    @Override
139    boolean evolve(Format newFormatParam, Evolver evolver) {
140        if (!(newFormatParam instanceof EnumFormat)) {
141            evolver.addEvolveError
142                (this, newFormatParam,
143                 "Incompatible enum type changed detected",
144                 "An enum class may not be changed to a non-enum type");
145            /* For future:
146            evolver.addMissingMutation
147                (this, newFormatParam,
148                 "Converter is required when an enum class is changed to " +
149                 "a non-enum type");
150            */
151            return false;
152        }
153        EnumFormat newFormat = (EnumFormat) newFormatParam;
154        if (Arrays.equals(names, newFormat.names)) {
155            evolver.useOldFormat(this, newFormat);
156            return true;
157        } else {
158            Set<String> oldNames = new HashSet<String>(Arrays.asList(names));
159            List<String> newNames = Arrays.asList(newFormat.names);
160            if (newNames.containsAll(oldNames)) {
161                evolver.useEvolvedFormat(this, newFormat, newFormat);
162                return true;
163            } else {
164                oldNames.removeAll(newNames);
165                evolver.addEvolveError
166                    (this, newFormat,
167                     "Incompatible enum type changed detected",
168                     "Enum values may not be removed: " + oldNames);
169                /* For future:
170                evolver.addMissingMutation
171                    (this, newFormatParam,
172                     "Converter is required when a value is removed from an " +
173                     "enum: " + oldNames);
174                */
175                return false;
176            }
177        }
178    }
179}
180