/***************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. ****************************************************************/ package org.apache.cayenne.access.types; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.cayenne.util.Util; /** * Stores ExtendedTypes, implementing an algorithm to determine the right type * for a given Java class. See {@link #getRegisteredType(String)} documentation * for lookup algorithm details. */ public class ExtendedTypeMap { static final Map<String, String> classesForPrimitives; static { classesForPrimitives = new HashMap<>(); classesForPrimitives.put("long", Long.class.getName()); classesForPrimitives.put("double", Double.class.getName()); classesForPrimitives.put("byte", Byte.class.getName()); classesForPrimitives.put("boolean", Boolean.class.getName()); classesForPrimitives.put("float", Float.class.getName()); classesForPrimitives.put("short", Short.class.getName()); classesForPrimitives.put("int", Integer.class.getName()); } protected Map<String, String> typeAliases; protected final Map<String, ExtendedType> typeMap; protected ExtendedType defaultType; Collection<ExtendedTypeFactory> extendedTypeFactories; // standard type factories registered by Cayenne that are consulted after the user factories. Collection<ExtendedTypeFactory> internalTypeFactories; /** * Creates new ExtendedTypeMap, populating it with default JDBC-compatible * types. If JDK version is at least 1.5, also loads support for enumerated * types. */ public ExtendedTypeMap() { this.defaultType = new ObjectType(); this.typeMap = new ConcurrentHashMap<>(); this.typeAliases = new ConcurrentHashMap<>(classesForPrimitives); this.extendedTypeFactories = new CopyOnWriteArrayList<>(); this.internalTypeFactories = new CopyOnWriteArrayList<>(); initDefaultFactories(); } /** * Registers default factories for creating enum types and serializable * types. Note that user-defined factories are consulted before any default * factory. * * @since 3.0 */ protected void initDefaultFactories() { internalTypeFactories.add(new EnumTypeFactory()); internalTypeFactories.add(new ByteOrCharArrayFactory(this)); // note that Serializable type should be used as a last resort after all // other alternatives are exhausted. internalTypeFactories.add(new SerializableTypeFactory(this)); } /** * Adds an ExtendedTypeFactory that will be consulted if no direct mapping * for a given class exists. This feature can be used to map interfaces. * <p> * <i>Note that the order in which factories are added is important, as * factories are consulted in turn when an ExtendedType is looked up, and * lookup is stopped when any factory provides a non-null type.</i> * </p> * * @since 1.2 */ public void addFactory(ExtendedTypeFactory factory) { if (factory == null) { throw new IllegalArgumentException("Attempt to add null factory"); } extendedTypeFactories.add(factory); } /** * Removes a factory from the registered factories if it was previously * added. * * @since 1.2 */ public void removeFactory(ExtendedTypeFactory factory) { if (factory != null) { extendedTypeFactories.remove(factory); } } /** * Adds a new type to the list of registered types. If there is another type * registered for a class described by the <code>type</code> argument, the * old handler is overridden by the new one. */ public void registerType(ExtendedType type) { typeMap.put(type.getClassName(), type); // factory to handle subclasses of type.className addFactory(new SubclassTypeFactory(type)); } /** * Returns a default ExtendedType that is used to handle unmapped types. */ public ExtendedType getDefaultType() { return defaultType; } /** * Returns a guaranteed non-null ExtendedType instance for a given Java * class name. Primitive class names are internally replaced by the * non-primitive counterparts. The following lookup sequence is used to * determine the type: * <ul> * <li>First the methods checks for an ExtendedType explicitly registered * with the map for a given class name (most common types are registered by * Cayenne internally; users can register their own).</li> * <li>Second, the method tries to obtain a type by iterating through * {@link ExtendedTypeFactory} instances registered by users. If a factory * returns a non-null type, it is returned to the user and the rest of the * factories are ignored.</li> * <li>Third, the method iterates through standard * {@link ExtendedTypeFactory} instances that can dynamically construct * extended types for serializable objects and JDK 1.5 enums.</li> * <li>If all the methods above failed, the default type is returned that * relies on default JDBC driver mapping to set and get objects.</li> * </ul> * <i>Note that for array types class name must be in the form * 'MyClass[]'</i>. */ public ExtendedType getRegisteredType(String javaClassName) { if (javaClassName == null) { return getDefaultType(); } javaClassName = canonicalizedTypeName(javaClassName); ExtendedType type = getExplictlyRegisteredType(javaClassName); if (type != null) { return type; } type = createType(javaClassName); if (type != null) { // register to speed up future access registerType(type); return type; } return getDefaultType(); } ExtendedType getExplictlyRegisteredType(String className) { if (className == null) { throw new NullPointerException("Null className"); } return typeMap.get(className); } /** * Returns a type registered for the class name. If no such type exists, * returns the default type. It is guaranteed that this method returns a * non-null ExtendedType instance. */ public ExtendedType getRegisteredType(Class<?> javaClass) { return getRegisteredType(javaClass.getCanonicalName()); } /** * Removes registered ExtendedType object corresponding to * <code>javaClassName</code> parameter. */ public void unregisterType(String javaClassName) { typeMap.remove(javaClassName); } /** * Returns array of Java class names supported by Cayenne for JDBC mapping. */ public String[] getRegisteredTypeNames() { Set<String> keys = typeMap.keySet(); int len = keys.size(); String[] types = new String[len]; Iterator<String> it = keys.iterator(); for (int i = 0; i < len; i++) { types[i] = it.next(); } return types; } /** * Returns an ExtendedType for specific Java classes. Uses user-provided and * Cayenne-provided {@link ExtendedTypeFactory} factories to instantiate the * ExtendedType. All primitive classes must be converted to the * corresponding Java classes by the callers. * * @return a default type for a given class or null if a class has no * default type mapping. * @since 1.2 */ protected ExtendedType createType(String className) { if (className == null) { return null; } Class<?> typeClass; try { typeClass = Util.getJavaClass(className); } catch (Throwable th) { // ignore exceptions... return null; } // lookup in user factories first for (ExtendedTypeFactory factory : extendedTypeFactories) { ExtendedType type = factory.getType(typeClass); if (type != null) { return type; } } // lookup in internal factories for (ExtendedTypeFactory factory : internalTypeFactories) { ExtendedType type = factory.getType(typeClass); if (type != null) { return type; } } return null; } /** * For the class name returns a name "canonicalized" for the purpose of * ExtendedType lookup. * * @since 4.0 */ protected String canonicalizedTypeName(String className) { String canonicalized = typeAliases.get(className); if (canonicalized == null) { int index = className.indexOf('$'); if (index >= 0) { canonicalized = className.replace('$', '.'); } else { canonicalized = className; } typeAliases.put(className, canonicalized); } return canonicalized; } }