001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006    * See the AUTHORS.txt file in the distribution for a full listing of 
007    * individual contributors.
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.graph.property;
025    
026    import java.math.BigDecimal;
027    import java.net.URI;
028    import java.util.ArrayList;
029    import java.util.Arrays;
030    import java.util.Collections;
031    import java.util.Comparator;
032    import java.util.HashSet;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Set;
036    import java.util.UUID;
037    import net.jcip.annotations.Immutable;
038    import org.jboss.dna.common.util.CheckArg;
039    import org.jboss.dna.graph.GraphI18n;
040    
041    /**
042     * @author Randall Hauch
043     * @author John Verhaeg
044     */
045    @Immutable
046    public enum PropertyType {
047    
048        STRING("String", ValueComparators.STRING_COMPARATOR, new ObjectCanonicalizer(), String.class),
049        BINARY("Binary", ValueComparators.BINARY_COMPARATOR, new ObjectCanonicalizer(), Binary.class),
050        LONG("Long", ValueComparators.LONG_COMPARATOR, new LongCanonicalizer(), Long.class, Integer.class, Short.class),
051        DOUBLE("Double", ValueComparators.DOUBLE_COMPARATOR, new DoubleCanonicalizer(), Double.class, Float.class),
052        DECIMAL("Decimal", ValueComparators.DECIMAL_COMPARATOR, new ObjectCanonicalizer(), BigDecimal.class),
053        DATE("Date", ValueComparators.DATE_TIME_COMPARATOR, new ObjectCanonicalizer(), DateTime.class),
054        BOOLEAN("Boolean", ValueComparators.BOOLEAN_COMPARATOR, new ObjectCanonicalizer(), Boolean.class),
055        NAME("Name", ValueComparators.NAME_COMPARATOR, new ObjectCanonicalizer(), Name.class),
056        PATH("Path", ValueComparators.PATH_COMPARATOR, new ObjectCanonicalizer(), Path.class),
057        UUID("UUID", ValueComparators.UUID_COMPARATOR, new ObjectCanonicalizer(), UUID.class),
058        REFERENCE("Reference", ValueComparators.REFERENCE_COMPARATOR, new ObjectCanonicalizer(), Reference.class),
059        URI("URI", ValueComparators.URI_COMPARATOR, new ObjectCanonicalizer(), URI.class),
060        OBJECT("Object", ValueComparators.OBJECT_COMPARATOR, new ObjectCanonicalizer(), Object.class);
061    
062        private static interface Canonicalizer {
063            Object canonicalizeValue( Object value );
064        }
065    
066        protected final static class ObjectCanonicalizer implements Canonicalizer {
067            public Object canonicalizeValue( Object value ) {
068                return value;
069            }
070        }
071    
072        protected final static class LongCanonicalizer implements Canonicalizer {
073            public Object canonicalizeValue( Object value ) {
074                if (value instanceof Integer) return new Long((Integer)value);
075                if (value instanceof Short) return new Long((Short)value);
076                return value;
077            }
078        }
079    
080        protected final static class DoubleCanonicalizer implements Canonicalizer {
081            public Object canonicalizeValue( Object value ) {
082                if (value instanceof Float) return new Double((Float)value);
083                return value;
084            }
085        }
086    
087        private static final List<PropertyType> ALL_PROPERTY_TYPES;
088        static {
089            List<PropertyType> types = new ArrayList<PropertyType>();
090            for (PropertyType type : PropertyType.values()) {
091                types.add(type);
092            }
093            ALL_PROPERTY_TYPES = Collections.unmodifiableList(types);
094        }
095    
096        private final String name;
097        private final Comparator<?> comparator;
098        private final Canonicalizer canonicalizer;
099        private final Class<?> valueClass;
100        private final Set<Class<?>> castableValueClasses;
101    
102        private PropertyType( String name,
103                              Comparator<?> comparator,
104                              Canonicalizer canonicalizer,
105                              Class<?> valueClass,
106                              Class<?>... castableClasses ) {
107            this.name = name;
108            this.comparator = comparator;
109            this.canonicalizer = canonicalizer;
110            this.valueClass = valueClass;
111            if (castableClasses != null && castableClasses.length != 0) {
112                castableValueClasses = Collections.unmodifiableSet(new HashSet<Class<?>>(Arrays.asList(castableClasses)));
113            } else {
114                castableValueClasses = Collections.emptySet();
115            }
116        }
117    
118        public Class<?> getValueClass() {
119            return this.valueClass;
120        }
121    
122        public String getName() {
123            return this.name;
124        }
125    
126        public Comparator<?> getComparator() {
127            return this.comparator;
128        }
129    
130        /**
131         * Obtain a value of this type in its canonical form. Some property types allow values to be instances of the canonical class
132         * or an alternative class. This method ensures that the value is always an instance of the canonical class.
133         * <p>
134         * Note that this method does <i>not</i> cast from one property type to another.
135         * </p>
136         * 
137         * @param value the property value
138         * @return the value in canonical form
139         */
140        public Object getCanonicalValue( Object value ) {
141            return this.canonicalizer.canonicalizeValue(value);
142        }
143    
144        public final boolean isTypeFor( Object value ) {
145            if (this.valueClass.isInstance(value)) return true;
146            for (Class<?> valueClass : castableValueClasses) {
147                if (valueClass.isInstance(value)) return true;
148            }
149            return false;
150        }
151    
152        public final boolean isTypeForEach( Iterable<?> values ) {
153            for (Object value : values) {
154                if (!isTypeFor(value)) return false;
155            }
156            return true;
157        }
158    
159        public final boolean isTypeForEach( Iterator<?> values ) {
160            while (values.hasNext()) {
161                Object value = values.next();
162                if (!isTypeFor(value)) return false;
163            }
164            return true;
165        }
166    
167        public static PropertyType discoverType( Object value ) {
168            if (value == null) {
169                throw new IllegalArgumentException(GraphI18n.unableToDiscoverPropertyTypeForNullValue.text());
170            }
171            for (PropertyType type : PropertyType.values()) {
172                if (type == OBJECT) continue;
173                if (type.isTypeFor(value)) return type;
174            }
175            return OBJECT;
176        }
177    
178        /**
179         * Discover the most appropriate {@link PropertyType} whose values can be assigned to variables or parameters of the supplied
180         * type. This method does check whether the supplied {@link Class} is an array, in which case it just evalutes the
181         * {@link Class#getComponentType() component type} of the array.
182         * 
183         * @param clazz the class representing the type of a value or parameter; may not be null
184         * @return the PropertyType that best represents the type whose values can be used as a value in the supplied class, or null
185         *         if no matching PropertyType could be found
186         */
187        public static PropertyType discoverType( Class<?> clazz ) {
188            CheckArg.isNotNull(clazz, "clazz");
189            // Is the supplied class an array (or an array of arrays)?
190            while (clazz.isArray()) {
191                // Then just call extract the component type that of which we have an array ...
192                clazz = clazz.getComponentType();
193            }
194            // Try each property type, and see if its value type is an exact match ...
195            for (PropertyType type : PropertyType.values()) {
196                if (type.valueClass.equals(clazz)) return type;
197                // If the property type is capable of handling a primitive ...
198                switch (type) {
199                    case LONG:
200                        if (Long.TYPE.equals(clazz) || Integer.TYPE.equals(clazz) || Short.TYPE.equals(clazz)) return type;
201                        if (Integer.class.equals(clazz) || Short.class.equals(clazz)) return type;
202                        break;
203                    case DOUBLE:
204                        if (Double.TYPE.equals(clazz) || Float.TYPE.equals(clazz)) return type;
205                        if (Float.class.equals(clazz)) return type;
206                        break;
207                    case BOOLEAN:
208                        if (Boolean.TYPE.equals(clazz)) return type;
209                        break;
210                    default:
211                        break;
212                }
213            }
214            // No value class of the property type matched exactly, so now see if any property type is assignable to 'clazz' ...
215            for (PropertyType type : PropertyType.values()) {
216                if (clazz.isAssignableFrom(type.valueClass)) return type;
217            }
218            // Nothing works, ...
219            return null;
220        }
221    
222        /**
223         * Return an iterator over all the property type enumeration literals.
224         * 
225         * @return an immutable iterator
226         */
227        public static Iterator<PropertyType> iterator() {
228            return ALL_PROPERTY_TYPES.iterator();
229        }
230    }