View Javadoc

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 }