1 /* 2 * Entreri, an entity-component framework in Java 3 * 4 * Copyright (c) 2013, Michael Ludwig 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without modification, 8 * are permitted provided that the following conditions are met: 9 * 10 * Redistributions of source code must retain the above copyright notice, 11 * this list of conditions and the following disclaimer. 12 * Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 package com.lhkbob.entreri.impl; 28 29 import com.lhkbob.entreri.property.*; 30 31 import java.io.BufferedReader; 32 import java.io.IOException; 33 import java.io.InputStreamReader; 34 import java.net.URL; 35 import java.util.Enumeration; 36 import java.util.concurrent.ConcurrentHashMap; 37 38 /** 39 * TypePropertyMapping is an internal class used to maintain a thread-safe, shared, and 40 * consistent mapping from Java type to an associated Property type that wraps that data. 41 * Primitive and plain Object wrapping is built-in. A property can be overridden for a 42 * class by placing the file META-INF/entreri/mapping/<CANONICAL CLASS NAME> in the 43 * class path, with a single string <BINARY CLASS NAME OF PROPERTY>. 44 * 45 * @author Michael Ludwig 46 */ 47 public final class TypePropertyMapping { 48 public static final String MAPPING_DIR = "META-INF/entreri/mapping/"; 49 50 private static final ConcurrentHashMap<Class<?>, Class<? extends Property>> typeMapping; 51 52 static { 53 typeMapping = new ConcurrentHashMap<>(); 54 55 // default mapping for primitive types 56 typeMapping.put(byte.class, ByteProperty.class); 57 typeMapping.put(short.class, ShortProperty.class); 58 typeMapping.put(int.class, IntProperty.class); 59 typeMapping.put(long.class, LongProperty.class); 60 typeMapping.put(float.class, FloatProperty.class); 61 typeMapping.put(double.class, DoubleProperty.class); 62 typeMapping.put(char.class, CharProperty.class); 63 typeMapping.put(boolean.class, BooleanProperty.class); 64 typeMapping.put(Object.class, ObjectProperty.class); 65 } 66 67 private TypePropertyMapping() { 68 } 69 70 /** 71 * Attempt to determine a property class that wraps the corresponding Java type. If it 72 * is a primitive type, it will use the corresponding primitive wrapper defined in 73 * com.lhkbob.entreri.property. 74 * <p/> 75 * Unless the type has an available mapping file, it will fallback to ObjectProperty. 76 * 77 * @param type The Java type that needs to be wrapped as a property 78 * 79 * @return The discovered or cached property type for the given type 80 */ 81 @SuppressWarnings("unchecked") 82 public static Class<? extends Property> getPropertyForType(Class<?> type) { 83 Class<? extends Property> pType = typeMapping.get(type); 84 if (pType != null) { 85 // already mapped file exists (statically or from previous META-INF load) 86 return pType; 87 } 88 89 ClassLoader loader = type.getClassLoader(); 90 if (loader != null) { 91 try { 92 // otherwise check if we have a properties file to load 93 Enumeration<URL> urls = loader 94 .getResources(MAPPING_DIR + type.getCanonicalName()); 95 if (urls.hasMoreElements()) { 96 URL mapping = urls.nextElement(); 97 if (urls.hasMoreElements()) { 98 throw new RuntimeException("Multiple mapping files for " + type + 99 " present in classpath"); 100 } 101 102 103 BufferedReader in = new BufferedReader( 104 new InputStreamReader(mapping.openStream())); 105 String line; 106 StringBuilder className = new StringBuilder(); 107 // be somewhat permissive of whitespace (any other input most likely 108 // will fail to load a class) 109 while ((line = in.readLine()) != null) { 110 className.append(line); 111 } 112 in.close(); 113 114 // use the type's class loader so that the loaded property is tied to 115 // the same loader 116 try { 117 pType = (Class<? extends Property>) type.getClassLoader() 118 .loadClass(className 119 .toString() 120 .trim()); 121 122 // store the mapping for later as well, this is safe because 123 // a class's loader is part of its identity and the property impl 124 // will use the same loader; even if we concurrently modify it, 125 // the same value will be stored 126 typeMapping.put(type, pType); 127 } catch (ClassNotFoundException e) { 128 throw new RuntimeException( 129 "Unable to load mapped Property class for " + type, e); 130 } 131 } 132 } catch (IOException e) { 133 throw new RuntimeException( 134 "Error reading META-INF mapping for class: " + type, e); 135 } 136 } 137 138 if (pType == null) { 139 // generic fallback 140 pType = ObjectProperty.class; 141 } 142 143 return pType; 144 } 145 }