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 }