j256 / ormlite-android

@@ -1,60 +1,26 @@
Loading
1 1
package com.j256.ormlite.android;
2 2
3 3
import java.lang.reflect.Field;
4 -
import java.lang.reflect.InvocationHandler;
5 -
import java.lang.reflect.Proxy;
6 4
import java.sql.SQLException;
7 5
import java.util.ArrayList;
8 6
import java.util.List;
9 7
10 8
import com.j256.ormlite.db.DatabaseType;
11 -
import com.j256.ormlite.field.DataPersister;
12 -
import com.j256.ormlite.field.DataType;
13 9
import com.j256.ormlite.field.DatabaseField;
14 10
import com.j256.ormlite.field.DatabaseFieldConfig;
15 11
import com.j256.ormlite.support.ConnectionSource;
16 12
import com.j256.ormlite.table.DatabaseTableConfig;
17 13
18 14
/**
19 -
 * Class which uses reflection to make the job of processing the {@link DatabaseField} annotation more efficient. In
20 -
 * current (as of 11/2011) versions of Android, Annotations are ghastly slow. This uses reflection on the Android
21 -
 * classes to work around this issue. Gross and a hack but a significant (~20x) performance improvement.
22 -
 * 
23 -
 * <p>
24 -
 * Thanks much go to Josh Guilfoyle for the idea and the code framework to make this happen.
25 -
 * </p>
15 +
 * Class which extracts the table-config from a class. This use to be a reflection hack to make the job of processing
16 +
 * the {@link DatabaseField} annotation more efficient. In past versions of Android before ice-cream-sandwich (think
17 +
 * 11/2011), annotations were ghastly slow and the hack was a bit gross but yielded a significant (~20x) performance
18 +
 * improvement. Thanks to Josh Guilfoyle for them.
26 19
 * 
27 20
 * @author joshguilfoyle, graywatson
28 21
 */
29 22
public class DatabaseTableConfigUtil {
30 23
31 -
	/**
32 -
	 * Set this system property to any value to disable the annotations hack which seems to cause problems on certain
33 -
	 * operating systems.
34 -
	 */
35 -
	public static final String DISABLE_ANNOTATION_HACK_SYSTEM_PROPERTY = "ormlite.annotation.hack.disable";
36 -
37 -
	private static Class<?> annotationFactoryClazz;
38 -
	private static Field elementsField;
39 -
	private static Class<?> annotationMemberClazz;
40 -
	private static Field nameField;
41 -
	private static Field valueField;
42 -
	private static int workedC = 0;
43 -
44 -
	private static final int[] configFieldNums;
45 -
46 -
	static {
47 -
		/*
48 -
		 * If we are dealing with older versions of the OS and if we've not disabled the annotation hack...
49 -
		 */
50 -
		if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH
51 -
				&& System.getProperty(DISABLE_ANNOTATION_HACK_SYSTEM_PROPERTY) == null) {
52 -
			configFieldNums = lookupClasses();
53 -
		} else {
54 -
			configFieldNums = null;
55 -
		}
56 -
	}
57 -
58 24
	/**
59 25
	 * Build our list table config from a class using some annotation fu around.
60 26
	 */
@@ -65,7 +31,7 @@
Loading
65 31
		List<DatabaseFieldConfig> fieldConfigs = new ArrayList<DatabaseFieldConfig>();
66 32
		for (Class<?> classWalk = clazz; classWalk != null; classWalk = classWalk.getSuperclass()) {
67 33
			for (Field field : classWalk.getDeclaredFields()) {
68 -
				DatabaseFieldConfig config = configFromField(databaseType, tableName, field);
34 +
				DatabaseFieldConfig config = DatabaseFieldConfig.fromField(databaseType, tableName, field);
69 35
				if (config != null && config.isPersisted()) {
70 36
					fieldConfigs.add(config);
71 37
				}
@@ -77,365 +43,4 @@
Loading
77 43
			return new DatabaseTableConfig<T>(clazz, tableName, fieldConfigs);
78 44
		}
79 45
	}
80 -
81 -
	/**
82 -
	 * Return the number of fields configured using our reflection hack. This is for testing.
83 -
	 */
84 -
	public static int getWorkedC() {
85 -
		return workedC;
86 -
	}
87 -
88 -
	/**
89 -
	 * This does all of the class reflection fu to find our classes, find the order of field names, and construct our
90 -
	 * array of ConfigField entries the correspond to the AnnotationMember array.
91 -
	 */
92 -
	private static int[] lookupClasses() {
93 -
		Class<?> annotationMemberArrayClazz;
94 -
		try {
95 -
			annotationFactoryClazz = Class.forName("org.apache.harmony.lang.annotation.AnnotationFactory");
96 -
			annotationMemberClazz = Class.forName("org.apache.harmony.lang.annotation.AnnotationMember");
97 -
			annotationMemberArrayClazz = Class.forName("[Lorg.apache.harmony.lang.annotation.AnnotationMember;");
98 -
		} catch (ClassNotFoundException e) {
99 -
			return null;
100 -
		}
101 -
102 -
		Field fieldField;
103 -
		try {
104 -
			elementsField = annotationFactoryClazz.getDeclaredField("elements");
105 -
			elementsField.setAccessible(true);
106 -
107 -
			nameField = annotationMemberClazz.getDeclaredField("name");
108 -
			nameField.setAccessible(true);
109 -
			valueField = annotationMemberClazz.getDeclaredField("value");
110 -
			valueField.setAccessible(true);
111 -
112 -
			fieldField = DatabaseFieldSample.class.getDeclaredField("field");
113 -
		} catch (SecurityException e) {
114 -
			return null;
115 -
		} catch (NoSuchFieldException e) {
116 -
			return null;
117 -
		}
118 -
119 -
		DatabaseField databaseField = fieldField.getAnnotation(DatabaseField.class);
120 -
		InvocationHandler proxy = Proxy.getInvocationHandler(databaseField);
121 -
		if (proxy.getClass() != annotationFactoryClazz) {
122 -
			return null;
123 -
		}
124 -
125 -
		try {
126 -
			// this should be an array of AnnotationMember objects
127 -
			Object elements = elementsField.get(proxy);
128 -
			if (elements == null || elements.getClass() != annotationMemberArrayClazz) {
129 -
				return null;
130 -
			}
131 -
132 -
			Object[] elementArray = (Object[]) elements;
133 -
			int[] configNums = new int[elementArray.length];
134 -
135 -
			// build our array of field-numbers that match the AnnotationMember array
136 -
			for (int i = 0; i < elementArray.length; i++) {
137 -
				String name = (String) nameField.get(elementArray[i]);
138 -
				configNums[i] = configFieldNameToNum(name);
139 -
			}
140 -
			return configNums;
141 -
		} catch (IllegalAccessException e) {
142 -
			return null;
143 -
		}
144 -
	}
145 -
146 -
	/*
147 -
	 * NOTE: we are doing this instead of an enum (which otherwise would be much better) because we don't want to take
148 -
	 * the class-size hit that comes with each enum being its own class.
149 -
	 */
150 -
	private static final int COLUMN_NAME = 1;
151 -
	private static final int DATA_TYPE = 2;
152 -
	private static final int DEFAULT_VALUE = 3;
153 -
	private static final int WIDTH = 4;
154 -
	private static final int CAN_BE_NULL = 5;
155 -
	private static final int ID = 6;
156 -
	private static final int GENERATED_ID = 7;
157 -
	private static final int GENERATED_ID_SEQUENCE = 8;
158 -
	private static final int FOREIGN = 9;
159 -
	private static final int USE_GET_SET = 10;
160 -
	private static final int UNKNOWN_ENUM_NAME = 11;
161 -
	private static final int THROW_IF_NULL = 12;
162 -
	private static final int PERSISTED = 13;
163 -
	private static final int FORMAT = 14;
164 -
	private static final int UNIQUE = 15;
165 -
	private static final int UNIQUE_COMBO = 16;
166 -
	private static final int INDEX = 17;
167 -
	private static final int UNIQUE_INDEX = 18;
168 -
	private static final int INDEX_NAME = 19;
169 -
	private static final int UNIQUE_INDEX_NAME = 20;
170 -
	private static final int FOREIGN_AUTO_REFRESH = 21;
171 -
	private static final int MAX_FOREIGN_AUTO_REFRESH_LEVEL = 22;
172 -
	private static final int PERSISTER_CLASS = 23;
173 -
	private static final int ALLOW_GENERATED_ID_INSERT = 24;
174 -
	private static final int COLUMN_DEFINITON = 25;
175 -
	private static final int FOREIGN_AUTO_CREATE = 26;
176 -
	private static final int VERSION = 27;
177 -
	private static final int FOREIGN_COLUMN_NAME = 28;
178 -
	private static final int READ_ONLY = 29;
179 -
	private static final int FULL_COLUMN_DEFINITON = 30;
180 -
	private static final int JAVAX_ENTITY = 31;
181 -
182 -
	/**
183 -
	 * Convert the name of the @DatabaseField fields into a number for easy processing later.
184 -
	 */
185 -
	private static int configFieldNameToNum(String configName) {
186 -
		if (configName.equals("columnName")) {
187 -
			return COLUMN_NAME;
188 -
		} else if (configName.equals("dataType")) {
189 -
			return DATA_TYPE;
190 -
		} else if (configName.equals("defaultValue")) {
191 -
			return DEFAULT_VALUE;
192 -
		} else if (configName.equals("width")) {
193 -
			return WIDTH;
194 -
		} else if (configName.equals("canBeNull")) {
195 -
			return CAN_BE_NULL;
196 -
		} else if (configName.equals("id")) {
197 -
			return ID;
198 -
		} else if (configName.equals("generatedId")) {
199 -
			return GENERATED_ID;
200 -
		} else if (configName.equals("generatedIdSequence")) {
201 -
			return GENERATED_ID_SEQUENCE;
202 -
		} else if (configName.equals("foreign")) {
203 -
			return FOREIGN;
204 -
		} else if (configName.equals("useGetSet")) {
205 -
			return USE_GET_SET;
206 -
		} else if (configName.equals("unknownEnumName")) {
207 -
			return UNKNOWN_ENUM_NAME;
208 -
		} else if (configName.equals("throwIfNull")) {
209 -
			return THROW_IF_NULL;
210 -
		} else if (configName.equals("persisted")) {
211 -
			return PERSISTED;
212 -
		} else if (configName.equals("format")) {
213 -
			return FORMAT;
214 -
		} else if (configName.equals("unique")) {
215 -
			return UNIQUE;
216 -
		} else if (configName.equals("uniqueCombo")) {
217 -
			return UNIQUE_COMBO;
218 -
		} else if (configName.equals("index")) {
219 -
			return INDEX;
220 -
		} else if (configName.equals("uniqueIndex")) {
221 -
			return UNIQUE_INDEX;
222 -
		} else if (configName.equals("indexName")) {
223 -
			return INDEX_NAME;
224 -
		} else if (configName.equals("uniqueIndexName")) {
225 -
			return UNIQUE_INDEX_NAME;
226 -
		} else if (configName.equals("foreignAutoRefresh")) {
227 -
			return FOREIGN_AUTO_REFRESH;
228 -
		} else if (configName.equals("maxForeignAutoRefreshLevel")) {
229 -
			return MAX_FOREIGN_AUTO_REFRESH_LEVEL;
230 -
		} else if (configName.equals("persisterClass")) {
231 -
			return PERSISTER_CLASS;
232 -
		} else if (configName.equals("allowGeneratedIdInsert")) {
233 -
			return ALLOW_GENERATED_ID_INSERT;
234 -
		} else if (configName.equals("columnDefinition")) {
235 -
			return COLUMN_DEFINITON;
236 -
		} else if (configName.equals("fullColumnDefinition")) {
237 -
			return FULL_COLUMN_DEFINITON;
238 -
		} else if (configName.equals("foreignAutoCreate")) {
239 -
			return FOREIGN_AUTO_CREATE;
240 -
		} else if (configName.equals("version")) {
241 -
			return VERSION;
242 -
		} else if (configName.equals("foreignColumnName")) {
243 -
			return FOREIGN_COLUMN_NAME;
244 -
		} else if (configName.equals("readOnly")) {
245 -
			return READ_ONLY;
246 -
		} else if (configName.equals("javaxEntity")) {
247 -
			return JAVAX_ENTITY;
248 -
		} else {
249 -
			throw new IllegalStateException("Could not find support for DatabaseField " + configName);
250 -
		}
251 -
	}
252 -
253 -
	/**
254 -
	 * Extract our configuration information from the field by looking for a {@link DatabaseField} annotation.
255 -
	 */
256 -
	private static DatabaseFieldConfig configFromField(DatabaseType databaseType, String tableName, Field field)
257 -
			throws SQLException {
258 -
259 -
		if (configFieldNums == null) {
260 -
			return DatabaseFieldConfig.fromField(databaseType, tableName, field);
261 -
		}
262 -
263 -
		/*
264 -
		 * This, unfortunately, we can't get around. This creates a AnnotationFactory, an array of AnnotationMember
265 -
		 * fields, and possibly another array of AnnotationMember values. This creates a lot of GC'd objects.
266 -
		 */
267 -
		DatabaseField databaseField = field.getAnnotation(DatabaseField.class);
268 -
269 -
		DatabaseFieldConfig config = null;
270 -
		try {
271 -
			if (databaseField != null) {
272 -
				config = buildConfig(databaseField, tableName, field);
273 -
			}
274 -
		} catch (Exception e) {
275 -
			// ignored so we will configure normally below
276 -
		}
277 -
278 -
		if (config == null) {
279 -
			/*
280 -
			 * We configure this the old way because we might be using javax annotations, have a ForeignCollectionField,
281 -
			 * or may still be using the deprecated annotations. At this point we know that there isn't a @DatabaseField
282 -
			 * or we can't do our reflection hacks for some reason.
283 -
			 */
284 -
			return DatabaseFieldConfig.fromField(databaseType, tableName, field);
285 -
		} else {
286 -
			workedC++;
287 -
			return config;
288 -
		}
289 -
	}
290 -
291 -
	/**
292 -
	 * Instead of calling the annotation methods directly, we peer inside the proxy and investigate the array of
293 -
	 * AnnotationMember objects stored by the AnnotationFactory.
294 -
	 */
295 -
	private static DatabaseFieldConfig buildConfig(DatabaseField databaseField, String tableName, Field field)
296 -
			throws Exception {
297 -
		InvocationHandler proxy = Proxy.getInvocationHandler(databaseField);
298 -
		if (proxy.getClass() != annotationFactoryClazz) {
299 -
			return null;
300 -
		}
301 -
		// this should be an array of AnnotationMember objects
302 -
		Object elementsObject = elementsField.get(proxy);
303 -
		if (elementsObject == null) {
304 -
			return null;
305 -
		}
306 -
		DatabaseFieldConfig config = new DatabaseFieldConfig(field.getName());
307 -
		Object[] objs = (Object[]) elementsObject;
308 -
		for (int i = 0; i < configFieldNums.length; i++) {
309 -
			Object value = valueField.get(objs[i]);
310 -
			if (value != null) {
311 -
				assignConfigField(configFieldNums[i], config, field, value);
312 -
			}
313 -
		}
314 -
		return config;
315 -
	}
316 -
317 -
	/**
318 -
	 * Converts from field/value from the {@link DatabaseField} annotation to {@link DatabaseFieldConfig} values. This
319 -
	 * is very specific to this annotation.
320 -
	 */
321 -
	private static void assignConfigField(int configNum, DatabaseFieldConfig config, Field field, Object value) {
322 -
		switch (configNum) {
323 -
			case COLUMN_NAME:
324 -
				config.setColumnName(valueIfNotBlank((String) value));
325 -
				break;
326 -
			case DATA_TYPE:
327 -
				config.setDataType((DataType) value);
328 -
				break;
329 -
			case DEFAULT_VALUE:
330 -
				String defaultValue = (String) value;
331 -
				if (!(defaultValue == null || defaultValue.equals(DatabaseField.DEFAULT_STRING))) {
332 -
					config.setDefaultValue(defaultValue);
333 -
				}
334 -
				break;
335 -
			case WIDTH:
336 -
				config.setWidth((Integer) value);
337 -
				break;
338 -
			case CAN_BE_NULL:
339 -
				config.setCanBeNull((Boolean) value);
340 -
				break;
341 -
			case ID:
342 -
				config.setId((Boolean) value);
343 -
				break;
344 -
			case GENERATED_ID:
345 -
				config.setGeneratedId((Boolean) value);
346 -
				break;
347 -
			case GENERATED_ID_SEQUENCE:
348 -
				config.setGeneratedIdSequence(valueIfNotBlank((String) value));
349 -
				break;
350 -
			case FOREIGN:
351 -
				config.setForeign((Boolean) value);
352 -
				break;
353 -
			case USE_GET_SET:
354 -
				config.setUseGetSet((Boolean) value);
355 -
				break;
356 -
			case UNKNOWN_ENUM_NAME:
357 -
				config.setUnknownEnumValue(DatabaseFieldConfig.findMatchingEnumVal(field, (String) value));
358 -
				break;
359 -
			case THROW_IF_NULL:
360 -
				config.setThrowIfNull((Boolean) value);
361 -
				break;
362 -
			case PERSISTED:
363 -
				config.setPersisted((Boolean) value);
364 -
				break;
365 -
			case FORMAT:
366 -
				config.setFormat(valueIfNotBlank((String) value));
367 -
				break;
368 -
			case UNIQUE:
369 -
				config.setUnique((Boolean) value);
370 -
				break;
371 -
			case UNIQUE_COMBO:
372 -
				config.setUniqueCombo((Boolean) value);
373 -
				break;
374 -
			case INDEX:
375 -
				config.setIndex((Boolean) value);
376 -
				break;
377 -
			case UNIQUE_INDEX:
378 -
				config.setUniqueIndex((Boolean) value);
379 -
				break;
380 -
			case INDEX_NAME:
381 -
				config.setIndexName(valueIfNotBlank((String) value));
382 -
				break;
383 -
			case UNIQUE_INDEX_NAME:
384 -
				config.setUniqueIndexName(valueIfNotBlank((String) value));
385 -
				break;
386 -
			case FOREIGN_AUTO_REFRESH:
387 -
				config.setForeignAutoRefresh((Boolean) value);
388 -
				break;
389 -
			case MAX_FOREIGN_AUTO_REFRESH_LEVEL:
390 -
				config.setMaxForeignAutoRefreshLevel((Integer) value);
391 -
				break;
392 -
			case PERSISTER_CLASS:
393 -
				@SuppressWarnings("unchecked")
394 -
				Class<? extends DataPersister> clazz = (Class<? extends DataPersister>) value;
395 -
				config.setPersisterClass(clazz);
396 -
				break;
397 -
			case ALLOW_GENERATED_ID_INSERT:
398 -
				config.setAllowGeneratedIdInsert((Boolean) value);
399 -
				break;
400 -
			case COLUMN_DEFINITON:
401 -
				config.setColumnDefinition(valueIfNotBlank((String) value));
402 -
				break;
403 -
			case FULL_COLUMN_DEFINITON:
404 -
				config.setFullColumnDefinition(valueIfNotBlank((String) value));
405 -
				break;
406 -
			case FOREIGN_AUTO_CREATE:
407 -
				config.setForeignAutoCreate((Boolean) value);
408 -
				break;
409 -
			case VERSION:
410 -
				config.setVersion((Boolean) value);
411 -
				break;
412 -
			case FOREIGN_COLUMN_NAME:
413 -
				config.setForeignColumnName(valueIfNotBlank((String) value));
414 -
				break;
415 -
			case READ_ONLY:
416 -
				config.setReadOnly((Boolean) value);
417 -
				break;
418 -
			case JAVAX_ENTITY:
419 -
				config.setJavaxEntity((Boolean) value);
420 -
				break;
421 -
			default:
422 -
				throw new IllegalStateException("Could not find support for DatabaseField number " + configNum);
423 -
		}
424 -
	}
425 -
426 -
	private static String valueIfNotBlank(String value) {
427 -
		if (value == null || value.length() == 0) {
428 -
			return null;
429 -
		} else {
430 -
			return value;
431 -
		}
432 -
	}
433 -
434 -
	/**
435 -
	 * Class used to investigate the @DatabaseField annotation.
436 -
	 */
437 -
	private static class DatabaseFieldSample {
438 -
		@DatabaseField
439 -
		String field;
440 -
	}
441 46
}
Files Complexity Coverage
src/main/java/com/j256/ormlite 6.63% 8.18%
Project Totals (24 files) 6.63% 8.18%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading