Commit c1a82b27 authored by Ricki Hirner's avatar Ricki Hirner

add improved Property.get() with reflection support; refactoring

parent ae82dfe9
Pipeline #15678862 passed with stages
in 2 minutes and 13 seconds
......@@ -92,8 +92,8 @@ public class DavResourceTest {
.setBody(sampleText));
ResponseBody body = dav.get("*/*");
assertEquals(sampleText, body.string());
assertEquals("My Weak ETag", ((GetETag)dav.getProperties().get(GetETag.NAME)).getETag());
assertEquals("application/x-test-result", ((GetContentType) dav.getProperties().get(GetContentType.NAME)).getType());
assertEquals("My Weak ETag", dav.getProperties().get(GetETag.class).getETag());
assertEquals("application/x-test-result", dav.getProperties().get(GetContentType.class).getType());
RecordedRequest rq = mockServer.takeRequest();
assertEquals("GET", rq.getMethod());
......@@ -111,9 +111,9 @@ public class DavResourceTest {
.setBody(sampleText));
body = dav.get("*/*");
assertEquals(sampleText, body.string());
assertEquals("StrongETag", ((GetETag) dav.getProperties().get(GetETag.NAME)).getETag());
assertEquals("StrongETag", dav.getProperties().get(GetETag.class).getETag());
rq = mockServer.takeRequest();
mockServer.takeRequest();
rq = mockServer.takeRequest();
assertEquals("GET", rq.getMethod());
assertEquals("/target", rq.getPath());
......@@ -124,7 +124,7 @@ public class DavResourceTest {
.setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(sampleText));
dav.get("*/*");
assertNull(dav.getProperties().get(GetETag.NAME));
assertNull(dav.getProperties().get(GetETag.class));
}
@Test
......@@ -139,7 +139,7 @@ public class DavResourceTest {
.setResponseCode(HttpURLConnection.HTTP_CREATED)
.setHeader("ETag", "W/\"Weak PUT ETag\""));
assertFalse(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, false));
assertEquals("Weak PUT ETag", ((GetETag)dav.getProperties().get(GetETag.NAME)).getETag());
assertEquals("Weak PUT ETag", dav.getProperties().get(GetETag.class).getETag());
RecordedRequest rq = mockServer.takeRequest();
assertEquals("PUT", rq.getMethod());
......@@ -155,9 +155,9 @@ public class DavResourceTest {
.setResponseCode(HttpURLConnection.HTTP_NO_CONTENT));
assertTrue(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, true));
assertEquals(url.resolve("/target"), dav.getLocation());
assertNull(dav.getProperties().get(GetETag.NAME));
assertNull(dav.getProperties().get(GetETag.class));
rq = mockServer.takeRequest();
mockServer.takeRequest();
rq = mockServer.takeRequest();
assertEquals("PUT", rq.getMethod());
assertEquals("*", rq.getHeader("If-None-Match"));
......@@ -328,7 +328,7 @@ public class DavResourceTest {
" </response>" +
"</multistatus>"));
dav.propfind(0, ResourceType.NAME);
assertNull(dav.getProperties().get(ResourceType.NAME));
assertNull(dav.getProperties().get(ResourceType.class));
/*** POSITIVE TESTS ***/
......@@ -374,7 +374,7 @@ public class DavResourceTest {
" </response>" +
"</multistatus>"));
dav.propfind(0, ResourceType.NAME, DisplayName.NAME);
assertEquals("My DAV Collection", ((DisplayName)dav.getProperties().get(DisplayName.NAME)).getDisplayName());
assertEquals("My DAV Collection", dav.getProperties().get(DisplayName.class).getDisplayName());
assertEquals(0, dav.getMembers().size());
// multi-status response for collection with several members; incomplete (not all <resourcetype>s listed)
......@@ -444,17 +444,17 @@ public class DavResourceTest {
boolean ok[] = new boolean[4];
for (DavResource member : dav.getMembers()) {
if (url.resolve("/dav/subcollection/").equals(member.getLocation())) {
assertTrue(((ResourceType) member.getProperties().get(ResourceType.NAME)).getTypes().contains(ResourceType.COLLECTION));
assertEquals("A Subfolder", ((DisplayName) member.getProperties().get(DisplayName.NAME)).getDisplayName());
assertTrue(member.getProperties().get(ResourceType.class).getTypes().contains(ResourceType.COLLECTION));
assertEquals("A Subfolder", member.getProperties().get(DisplayName.class).getDisplayName());
ok[0] = true;
} else if (url.resolve("/dav/uid@host:file").equals(member.getLocation())) {
assertEquals("Absolute path with @ and :", ((DisplayName)member.getProperties().get(DisplayName.NAME)).getDisplayName());
assertEquals("Absolute path with @ and :", member.getProperties().get(DisplayName.class).getDisplayName());
ok[1] = true;
} else if (url.resolve("/dav/relative-uid@host.file").equals(member.getLocation())) {
assertEquals("Relative path with @", ((DisplayName)member.getProperties().get(DisplayName.NAME)).getDisplayName());
assertEquals("Relative path with @", member.getProperties().get(DisplayName.class).getDisplayName());
ok[2] = true;
} else if (url.resolve("/dav/relative:colon.vcf").equals(member.getLocation())) {
assertEquals("Relative path with colon", ((DisplayName)member.getProperties().get(DisplayName.NAME)).getDisplayName());
assertEquals("Relative path with colon", member.getProperties().get(DisplayName.class).getDisplayName());
ok[3] = true;
}
}
......@@ -487,8 +487,8 @@ public class DavResourceTest {
" </response>" +
"</multistatus>"));
dav.propfind(0, ResourceType.NAME, DisplayName.NAME);
assertTrue(((ResourceType) dav.getProperties().get(ResourceType.NAME)).getTypes().contains(ResourceType.COLLECTION));
assertEquals("My DAV Collection", ((DisplayName) dav.getProperties().get(DisplayName.NAME)).getDisplayName());
assertTrue(dav.getProperties().get(ResourceType.class).getTypes().contains(ResourceType.COLLECTION));
assertEquals("My DAV Collection", dav.getProperties().get(DisplayName.class).getDisplayName());
// multi-status response with <propstat> that doesn't contain <status> (=> assume 200 OK)
mockServer.enqueue(new MockResponse()
......@@ -505,7 +505,7 @@ public class DavResourceTest {
" </response>" +
"</multistatus>"));
dav.propfind(0, DisplayName.NAME);
assertEquals("Without Status", ((DisplayName) dav.getProperties().get(DisplayName.NAME)).getDisplayName());
assertEquals("Without Status", dav.getProperties().get(DisplayName.class).getDisplayName());
}
@Test
......@@ -530,9 +530,9 @@ public class DavResourceTest {
" </response>" +
"</multistatus>"));
dav.propfind(0, DisplayName.NAME, GetETag.NAME, GetCTag.NAME);
assertEquals("DisplayName 1", ((DisplayName) dav.getProperties().get(DisplayName.NAME)).getDisplayName());
assertEquals("ETag 1", ((GetETag)dav.getProperties().get(GetETag.NAME)).getETag());
assertEquals("CTag 1", ((GetCTag) dav.getProperties().get(GetCTag.NAME)).getCTag());
assertEquals("DisplayName 1", dav.getProperties().get(DisplayName.class).getDisplayName());
assertEquals("ETag 1", dav.getProperties().get(GetETag.class).getETag());
assertEquals("CTag 1", dav.getProperties().get(GetCTag.class).getCTag());
mockServer.enqueue(new MockResponse()
.setResponseCode(207)
......@@ -555,9 +555,9 @@ public class DavResourceTest {
" </response>" +
"</multistatus>"));
dav.propfind(0, ResourceType.NAME, DisplayName.NAME);
assertEquals("DisplayName 2", ((DisplayName)dav.getProperties().get(DisplayName.NAME)).getDisplayName());
assertNull(dav.getProperties().get(GetETag.NAME));
assertEquals("CTag 1", ((GetCTag)dav.getProperties().get(GetCTag.NAME)).getCTag());
assertEquals("DisplayName 2", dav.getProperties().get(DisplayName.class).getDisplayName());
assertNull(dav.getProperties().get(GetETag.class));
assertEquals("CTag 1", dav.getProperties().get(GetCTag.class).getCTag());
}
}
......@@ -6,7 +6,7 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
package at.bitfire.dav4android
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
......
......@@ -495,7 +495,7 @@ open class DavResource @JvmOverloads constructor(
}
// if we know this resource is a collection, make sure href has a trailing slash (for clarity and resolving relative paths)
val type = properties[ResourceType.NAME] as ResourceType?
val type = properties[ResourceType::class.java]
if (type != null && type.types.contains(ResourceType.COLLECTION))
href = UrlUtils.withTrailingSlash(href)
......@@ -524,7 +524,7 @@ open class DavResource @JvmOverloads constructor(
hrefSegments = [ "davCollection", "aMember" ]
*/
if (hrefSegments.size > nBasePathSegments) {
val sameBasePath = (0..nBasePathSegments-1).none { locationSegments[it] != hrefSegments[it] }
val sameBasePath = (0 until nBasePathSegments).none { locationSegments[it] != hrefSegments[it] }
if (sameBasePath) {
target = DavResource(httpClient, href, log)
members.add(target)
......@@ -582,39 +582,39 @@ open class DavResource @JvmOverloads constructor(
// helpers
/** Finds first property within all responses (including unasked responses) */
fun findProperty(name: Property.Name): Pair<DavResource, Property>? {
fun<T: Property> findProperty(clazz: Class<T>): Pair<DavResource, T>? {
// check resource itself
val property = properties[name]
val property = properties[clazz]
if (property != null)
return Pair<DavResource, Property>(this, property)
return Pair(this, property)
// check members
for (member in members)
member.findProperty(name)?.let { return it }
member.findProperty(clazz)?.let { return it }
// check unrequested responses
for (resource in related)
resource.findProperty(name)?.let { return it }
resource.findProperty(clazz)?.let { return it }
return null
}
/** Finds properties within all responses (including unasked responses) */
fun findProperties(name: Property.Name): List<Pair<DavResource, Property>> {
val result = LinkedList<Pair<DavResource, Property>>()
fun<T: Property> findProperties(clazz: Class<T>): List<Pair<DavResource, T>> {
val result = LinkedList<Pair<DavResource, T>>()
// check resource itself
val property = properties[name]
val property = properties[clazz]
if (property != null)
result.add(Pair<DavResource, Property>(this, property))
result.add(Pair(this, property))
// check members
for (member in members)
result.addAll(member.findProperties(name))
result.addAll(member.findProperties(clazz))
// check unrequested responses
for (rel in related)
result.addAll(rel.findProperties(name))
result.addAll(rel.findProperties(clazz))
return Collections.unmodifiableList(result)
}
......
......@@ -8,6 +8,12 @@
package at.bitfire.dav4android
/**
* A WebDAV property.
*
* Every [Property] must define a static field called NAME of type Property.Name,
* which will be accessed by reflection.
*/
interface Property {
class Name(
......@@ -23,7 +29,7 @@ interface Property {
override fun hashCode() = namespace.hashCode() xor name.hashCode()
override fun toString() = "$name($namespace)"
override fun toString() = "$namespace$name"
}
}
......@@ -13,56 +13,56 @@ import java.util.Collections.unmodifiableMap
class PropertyCollection {
val properties = lazy { mutableMapOf<String, MutableMap<String, Property?>>() }
val properties = lazy { mutableMapOf<Property.Name, Property?>() }
operator fun get(name: Property.Name): Property? {
if (!properties.isInitialized())
return null
val nsProperties = properties.value[name.namespace] ?: return null
return nsProperties[name.name]
}
/**
* Returns a WebDAV property, or null if this property is not known.
* In most cases, using the alternative [get] with [Class] parameter is better because it provides type safety.
*/
operator fun get(name: Property.Name): Property? =
if (!properties.isInitialized())
null
else
properties.value[name]
fun getMap(): Map<Property.Name, Property?> {
/**
* Returns a WebDAV property, or null if this property is not known.
*/
operator fun<T: Property> get(clazz: Class<T>): T? {
if (!properties.isInitialized())
return mapOf()
return null
val map = HashMap<Property.Name, Property?>()
for ((namespace, nsProperties) in properties.value) {
for ((name, property) in nsProperties)
map[Property.Name(namespace, name)] = property
try {
val name = clazz.getDeclaredField("NAME").get(null) as Property.Name
return properties.value[name] as? T
} catch (e: NoSuchFieldException) {
Constants.log.severe("$clazz does not have a static NAME field")
return null
}
return unmodifiableMap(map)
}
operator fun set(name: Property.Name, property: Property?) {
var nsProperties = properties.value[name.namespace]
if (nsProperties == null) {
nsProperties = mutableMapOf<String, Property?>()
properties.value[name.namespace] = nsProperties
}
fun getMap(): Map<Property.Name, Property?> =
if (!properties.isInitialized())
mapOf()
else
unmodifiableMap(properties.value)
nsProperties[name.name] = property
operator fun set(name: Property.Name, property: Property?) {
properties.value[name] = property
}
operator fun minusAssign(name: Property.Name) {
if (!properties.isInitialized())
return
val nsProperties = properties.value[name.namespace]
nsProperties?.remove(name.name)
properties.value.remove(name)
}
fun size(): Int {
if (!properties.isInitialized())
return 0
var size = 0
for (nsProperties in properties.value.values)
size += nsProperties.size
return size
}
fun size() =
if (!properties.isInitialized())
0
else
properties.value.size
/**
......@@ -95,17 +95,16 @@ class PropertyCollection {
if (!properties.isInitialized())
return
for ((_, nsProperties) in properties.value) {
for (name in nsProperties.keys)
nsProperties.put(name, null)
}
val props = properties.value
for (name in props.keys)
props[name] = null
}
override fun toString(): String {
val s = LinkedList<String>()
for ((name, value) in getMap())
s.add("$name: $value")
s.add("$name = $value")
return "[${s.joinToString(", ")}]"
}
......
......@@ -6,7 +6,7 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
package at.bitfire.dav4android
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
......
......@@ -6,7 +6,7 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
package at.bitfire.dav4android
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
......@@ -18,7 +18,7 @@ object PropertyRegistry {
val factories = mutableMapOf<String /*namespace*/, MutableMap<String /*name*/, PropertyFactory>>()
init {
Constants.log.info("Registering DAV property factories");
Constants.log.info("Registering DAV property factories")
for (factory in ServiceLoader.load(PropertyFactory::class.java)) {
Constants.log.fine("Registering ${factory::class.java.name} for ${factory.getName()}")
register(factory)
......
......@@ -6,7 +6,7 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
package at.bitfire.dav4android
object QuotedStringUtils {
......
......@@ -6,7 +6,7 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
package at.bitfire.dav4android
import okhttp3.HttpUrl
import java.net.URI
......
......@@ -6,7 +6,7 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
package at.bitfire.dav4android
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
......
......@@ -6,6 +6,6 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android.exception;
package at.bitfire.dav4android.exception
open class DavException @JvmOverloads constructor(message: String, ex: Throwable? = null): Exception(message, ex)
\ No newline at end of file
......@@ -8,7 +8,7 @@
package at.bitfire.dav4android.exception
import okhttp3.Response;
import okhttp3.Response
import java.net.HttpURLConnection
class UnauthorizedException: HttpException {
......
......@@ -8,11 +8,10 @@
package at.bitfire.dav4android.property
import org.xmlpull.v1.XmlPullParser;
import at.bitfire.dav4android.Property;
import at.bitfire.dav4android.PropertyFactory;
import at.bitfire.dav4android.XmlUtils;
import at.bitfire.dav4android.Property
import at.bitfire.dav4android.PropertyFactory
import at.bitfire.dav4android.XmlUtils
import org.xmlpull.v1.XmlPullParser
data class AddressbookDescription(
var description: String? = null
......
......@@ -9,7 +9,6 @@
package at.bitfire.dav4android.property
import at.bitfire.dav4android.Property
import at.bitfire.dav4android.PropertyFactory
import at.bitfire.dav4android.XmlUtils
import org.xmlpull.v1.XmlPullParser
......@@ -21,7 +20,7 @@ class AddressbookHomeSet: HrefListProperty() {
}
class Factory(): HrefListProperty.Factory() {
class Factory : HrefListProperty.Factory() {
override fun getName() = NAME
......
......@@ -20,7 +20,7 @@ class CalendarHomeSet: HrefListProperty() {
}
class Factory(): HrefListProperty.Factory() {
class Factory : HrefListProperty.Factory() {
override fun getName() = NAME
......
......@@ -20,7 +20,7 @@ class CalendarProxyReadFor: HrefListProperty() {
}
class Factory(): HrefListProperty.Factory() {
class Factory : HrefListProperty.Factory() {
override fun getName() = NAME
......
......@@ -20,7 +20,7 @@ class CalendarProxyWriteFor: HrefListProperty() {
}
class Factory(): HrefListProperty.Factory() {
class Factory : HrefListProperty.Factory() {
override fun getName() = NAME
......
......@@ -20,7 +20,7 @@ class CalendarUserAddressSet: HrefListProperty() {
}
class Factory(): HrefListProperty.Factory() {
class Factory : HrefListProperty.Factory() {
override fun getName() = NAME
......
......@@ -23,7 +23,7 @@ data class DisplayName(
}
class Factory(): PropertyFactory {
class Factory : PropertyFactory {
override fun getName() = NAME
......
......@@ -23,7 +23,7 @@ data class GetCTag(
}
class Factory(): PropertyFactory {
class Factory : PropertyFactory {
override fun getName() = NAME
......
......@@ -26,7 +26,7 @@ data class GetContentType(
constructor(mediaType: MediaType): this(mediaType.toString())
class Factory(): PropertyFactory {
class Factory : PropertyFactory {
override fun getName() = NAME
......
......@@ -46,7 +46,7 @@ class GetETag(
override fun toString() = eTag ?: "(null)"
class Factory(): PropertyFactory {
class Factory : PropertyFactory {
override fun getName() = NAME
......
......@@ -20,7 +20,7 @@ class Source: HrefListProperty() {
}
class Factory(): HrefListProperty.Factory() {
class Factory : HrefListProperty.Factory() {
override fun getName() = NAME
......
......@@ -23,7 +23,7 @@ data class SyncToken(
}
class Factory(): PropertyFactory {
class Factory : PropertyFactory {
override fun getName() = NAME
......
......@@ -16,7 +16,6 @@ import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
@SuppressWarnings("ThrowableInstanceNeverThrown")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment