-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
src/com/perfectworldprogramming/mobile/orm/reflection/QueryParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package com.perfectworldprogramming.mobile.orm.reflection; | ||
|
||
import java.lang.reflect.Field; | ||
import java.util.List; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
import com.perfectworldprogramming.mobile.orm.annotations.Column; | ||
import com.perfectworldprogramming.mobile.orm.annotations.ForeignKey; | ||
import com.perfectworldprogramming.mobile.orm.annotations.PrimaryKey; | ||
import com.perfectworldprogramming.mobile.orm.exception.DataAccessException; | ||
import com.perfectworldprogramming.mobile.orm.exception.FieldNotFoundException; | ||
/** | ||
* Allows SQL to refer to domain classes and fields, which are then converted to SQL. | ||
* Queries place matchable tokens in square brackets and refer to either a direct field or a qualified field | ||
* using the (case insensitive) {@code class.getSimpleName()}. Duplicate fields will return the first match. | ||
* In the special case where only the simple class name is entered, the primary key field will be substituted. | ||
* This is to simplify queries using foreign keys. | ||
* eg [fieldName] or [MyClass.myField] <br/> | ||
* {@code "select * from Person where [age] = ?"}<br /> | ||
* {@code "select * from Person where [dateOfBirth] < ?"}<br /> | ||
* {@code "select * from Person where [Person.height] > ?"}<br /> | ||
* {@code "select * from Person p, Address a where p.[Person.height] > ? and p.[Person] = a.[Address.person]"}<br /> | ||
* @author David O'Meara <[email protected]> | ||
* @since 13/06/2012 | ||
* | ||
*/ | ||
public class QueryParser | ||
{ | ||
private QueryParser() | ||
{ | ||
// prevent external instantiation | ||
} | ||
|
||
public static String parse(String input, List<Class<?>> domainClasses) | ||
{ | ||
StringBuilder result = new StringBuilder(); | ||
int start = 0; | ||
Matcher m = p.matcher(input); | ||
while (m.find()) | ||
{ | ||
result.append(input.substring(start, m.start())); | ||
|
||
if (m.groupCount() == 2 && m.group(2).length()>0) | ||
{ | ||
for (Class<?> clazz : domainClasses) | ||
{ | ||
final String className = m.group(1); | ||
final String fieldName = m.group(2); | ||
if(clazz.getSimpleName().equalsIgnoreCase(className)) | ||
{ | ||
String replace = getColumnName(clazz, fieldName); | ||
if(replace==null || replace.length()==0) | ||
{ | ||
throw new FieldNotFoundException("Could not find field '"+fieldName+"' in class "+clazz.getName()); | ||
} | ||
result.append(replace); | ||
break; | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
final String matchName = m.group(1); | ||
String replace = null; | ||
for (Class<?> clazz : domainClasses) | ||
{ | ||
if(clazz.getSimpleName().equalsIgnoreCase(matchName)) | ||
{ | ||
PrimaryKey pk = new DomainClassAnalyzer().getPrimaryKey(clazz); | ||
replace = pk.value(); | ||
break; | ||
} | ||
} | ||
if(replace==null) | ||
{ | ||
for (Class<?> clazz : domainClasses) | ||
{ | ||
replace = getColumnName(clazz, matchName); | ||
if(replace.length()>0) | ||
{ | ||
break; | ||
} | ||
} | ||
} | ||
if(replace==null || replace.length()==0) | ||
{ | ||
throw new FieldNotFoundException("Could not find field '"+matchName+"' in domain class(es)"); | ||
} | ||
result.append(replace); | ||
} | ||
start = m.end(); | ||
} | ||
result.append(input.substring(start)); | ||
return result.toString(); | ||
} | ||
|
||
private static String getColumnName(Class<?> clazz, String fieldName) | ||
{ | ||
Field field; | ||
try | ||
{ | ||
field = clazz.getDeclaredField(fieldName); | ||
} | ||
catch (SecurityException e) | ||
{ | ||
throw new DataAccessException("Failed to view field "+fieldName+" in class "+clazz.getName(), e); | ||
} | ||
catch (NoSuchFieldException e) | ||
{ | ||
return ""; | ||
} | ||
if (field.isAnnotationPresent(Column.class)) | ||
{ | ||
Column setter = field.getAnnotation(Column.class); | ||
return setter.value(); | ||
} | ||
else if (field.isAnnotationPresent(PrimaryKey.class)) | ||
{ | ||
PrimaryKey setter = field.getAnnotation(PrimaryKey.class); | ||
return setter.value(); | ||
} | ||
else if (field.isAnnotationPresent(ForeignKey.class)) | ||
{ | ||
ForeignKey setter = field.getAnnotation(ForeignKey.class); | ||
return setter.value(); | ||
} | ||
return ""; | ||
} | ||
|
||
private static final String REGEX_SUBST = "\\[([a-zA-Z0-9]+)\\.?([a-zA-Z0-9]*)\\]"; | ||
private static final Pattern p = Pattern.compile(REGEX_SUBST); | ||
} |
146 changes: 146 additions & 0 deletions
146
test/com/perfectworldprogramming/mobile/orm/test/reflection/QueryParserTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package com.perfectworldprogramming.mobile.orm.test.reflection; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import android.test.ActivityInstrumentationTestCase2; | ||
|
||
import com.perfectworldprogramming.mobile.orm.exception.FieldNotFoundException; | ||
import com.perfectworldprogramming.mobile.orm.reflection.QueryParser; | ||
import com.perfectworldprogramming.mobile.orm.test.Main; | ||
import com.perfectworldprogramming.mobile.orm.test.domain.Address; | ||
import com.perfectworldprogramming.mobile.orm.test.domain.Person; | ||
|
||
public class QueryParserTest extends ActivityInstrumentationTestCase2<Main> | ||
{ | ||
public QueryParserTest() | ||
{ | ||
super("org.springframework.mobile.orm.test", Main.class); | ||
} | ||
|
||
public void testUnchanged() | ||
{ | ||
final String input = "select * from Person"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
assertEquals(input, QueryParser.parse(input, singleton)); | ||
} | ||
|
||
public void testUnchanged2() | ||
{ | ||
final String input = "select * from Person where FIRST_NAME = ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
assertEquals(input, QueryParser.parse(input, singleton)); | ||
} | ||
|
||
public void testSingleSimple() | ||
{ | ||
final String input = "select * from Person where [firstName] = ?"; | ||
final String expected = "select * from Person where FIRST_NAME = ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
final String actual = QueryParser.parse(input, singleton); | ||
assertEquals(expected, actual); | ||
} | ||
|
||
public void testMultipleSimple() | ||
{ | ||
final String input = "select * from Person where [firstName] = ? and [age] < ?"; | ||
final String expected = "select * from Person where FIRST_NAME = ? and AGE < ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
final String actual = QueryParser.parse(input, singleton); | ||
assertEquals(expected, actual); | ||
} | ||
|
||
public void testSingleComplex() | ||
{ | ||
final String input = "select * from Person where [Person.firstName] = ?"; | ||
final String expected = "select * from Person where FIRST_NAME = ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
final String actual = QueryParser.parse(input, singleton); | ||
assertEquals(expected, actual); | ||
} | ||
|
||
public void testMultipleComplex() | ||
{ | ||
final String input = "select * from Person where [Person.firstName] = ? and [Person.age] < ?"; | ||
final String expected = "select * from Person where FIRST_NAME = ? and AGE < ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
final String actual = QueryParser.parse(input, singleton); | ||
assertEquals(expected, actual); | ||
} | ||
|
||
public void testFailSimpleMatchInvalidField() | ||
{ | ||
final String input = "select * from Person where [abcdef] = ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
try | ||
{ | ||
QueryParser.parse(input, singleton); | ||
fail("FieldNotFound expected"); | ||
} | ||
catch(FieldNotFoundException e) | ||
{ | ||
// expected | ||
} | ||
} | ||
public void testFailSimpleMatchInvalidClass() | ||
{ | ||
final String input = "select * from Person where [age] = ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Address.class); | ||
try | ||
{ | ||
QueryParser.parse(input, singleton); | ||
fail("FieldNotFound expected"); | ||
} | ||
catch(FieldNotFoundException e) | ||
{ | ||
// expected | ||
} | ||
} | ||
|
||
public void testFailSimpleMatchInvalidClassAndField() | ||
{ | ||
final String input = "select * from Person where [Address.age] = ?"; | ||
List<Class<?>> singleton = new ArrayList<Class<?>>(); | ||
singleton.add(Person.class); | ||
singleton.add(Address.class); | ||
try | ||
{ | ||
QueryParser.parse(input, singleton); | ||
fail("FieldNotFound expected"); | ||
} | ||
catch(FieldNotFoundException e) | ||
{ | ||
// expected | ||
} | ||
} | ||
|
||
public void testMultipleClassesAndJoin() | ||
{ | ||
final String input = "select * from Person p, Address a where p.[lastName] = ? and p.[Person.id]=a.[Address.person]"; | ||
final String expected = "select * from Person p, Address a where p.LAST_NAME = ? and p.PERSON_ID=a.PERSON_ID"; | ||
List<Class<?>> list = new ArrayList<Class<?>>(); | ||
list.add(Person.class); | ||
list.add(Address.class); | ||
final String actual = QueryParser.parse(input, list); | ||
assertEquals(expected, actual); | ||
} | ||
|
||
public void testForeignKeyJoin() | ||
{ | ||
final String input = "select * from Person p, Address a where p.[Person]=a.[Address.person]"; | ||
final String expected = "select * from Person p, Address a where p.PERSON_ID=a.PERSON_ID"; | ||
List<Class<?>> list = new ArrayList<Class<?>>(); | ||
list.add(Person.class); | ||
list.add(Address.class); | ||
final String actual = QueryParser.parse(input, list); | ||
assertEquals(expected, actual); | ||
} | ||
} |