diff --git a/lib/src/main/java/net/mm2d/upnp/Device.java b/lib/src/main/java/net/mm2d/upnp/Device.java index 572d4a78..24017e11 100644 --- a/lib/src/main/java/net/mm2d/upnp/Device.java +++ b/lib/src/main/java/net/mm2d/upnp/Device.java @@ -39,7 +39,10 @@ public class Device { public static class Builder { @Nonnull private final ControlPoint mControlPoint; + @Nonnull private SsdpMessage mSsdpMessage; + @Nonnull + private String mLocation; private String mDescription; private String mUdn; private String mDeviceType; @@ -52,8 +55,10 @@ public static class Builder { private String mModelNumber; private String mSerialNumber; private String mPresentationUrl; - private List mIconBuilderList; - private List mServiceBuilderList; + @Nonnull + private List mIconBuilderList = Collections.emptyList(); + @Nonnull + private List mServiceBuilderList = Collections.emptyList(); @Nonnull private final Map> mTagMap; @@ -65,6 +70,11 @@ public static class Builder { */ public Builder(@Nonnull ControlPoint controlPoint, @Nonnull SsdpMessage ssdpMessage) { mControlPoint = controlPoint; + final String location = ssdpMessage.getLocation(); + if (location == null) { + throw new IllegalArgumentException(); + } + mLocation = location; mSsdpMessage = ssdpMessage; mTagMap = new LinkedHashMap<>(); mTagMap.put("", new HashMap()); @@ -75,8 +85,9 @@ public Builder(@Nonnull ControlPoint controlPoint, @Nonnull SsdpMessage ssdpMess * * @return SSDPに記述されたLocationの値 */ + @Nonnull public String getLocation() { - return mSsdpMessage.getLocation(); + return mLocation; } /** @@ -84,6 +95,7 @@ public String getLocation() { * * @return SSDPに記述されたUUID */ + @Nonnull public String getUuid() { return mSsdpMessage.getUuid(); } @@ -94,7 +106,13 @@ public String getUuid() { * @param ssdpMessage SSDPパケット * @return Builder */ + @Nonnull public Builder updateSsdpMessage(@Nonnull SsdpMessage ssdpMessage) { + final String location = ssdpMessage.getLocation(); + if (location == null) { + throw new IllegalArgumentException(); + } + mLocation = location; mSsdpMessage = ssdpMessage; return this; } @@ -105,6 +123,7 @@ public Builder updateSsdpMessage(@Nonnull SsdpMessage ssdpMessage) { * @param description DescriptionXML * @return Builder */ + @Nonnull public Builder setDescription(@Nonnull String description) { mDescription = description; return this; @@ -116,6 +135,7 @@ public Builder setDescription(@Nonnull String description) { * @param udn UDN * @return Builder */ + @Nonnull public Builder setUdn(@Nonnull String udn) { mUdn = udn; return this; @@ -127,6 +147,7 @@ public Builder setUdn(@Nonnull String udn) { * @param deviceType DeviceType * @return Builder */ + @Nonnull public Builder setDeviceType(@Nonnull String deviceType) { mDeviceType = deviceType; return this; @@ -138,6 +159,7 @@ public Builder setDeviceType(@Nonnull String deviceType) { * @param friendlyName FriendlyName * @return Builder */ + @Nonnull public Builder setFriendlyName(@Nonnull String friendlyName) { mFriendlyName = friendlyName; return this; @@ -149,6 +171,7 @@ public Builder setFriendlyName(@Nonnull String friendlyName) { * @param manufacture Manufacture * @return Builder */ + @Nonnull public Builder setManufacture(@Nonnull String manufacture) { mManufacture = manufacture; return this; @@ -160,6 +183,7 @@ public Builder setManufacture(@Nonnull String manufacture) { * @param manufactureUrl ManufactureUrl * @return Builder */ + @Nonnull public Builder setManufactureUrl(@Nonnull String manufactureUrl) { mManufactureUrl = manufactureUrl; return this; @@ -171,6 +195,7 @@ public Builder setManufactureUrl(@Nonnull String manufactureUrl) { * @param modelName ModelName * @return Builder */ + @Nonnull public Builder setModelName(@Nonnull String modelName) { mModelName = modelName; return this; @@ -182,6 +207,7 @@ public Builder setModelName(@Nonnull String modelName) { * @param modelUrl ModelUrl * @return Builder */ + @Nonnull public Builder setModelUrl(@Nonnull String modelUrl) { mModelUrl = modelUrl; return this; @@ -193,6 +219,7 @@ public Builder setModelUrl(@Nonnull String modelUrl) { * @param modelDescription ModelDescription * @return Builder */ + @Nonnull public Builder setModelDescription(@Nonnull String modelDescription) { mModelDescription = modelDescription; return this; @@ -204,6 +231,7 @@ public Builder setModelDescription(@Nonnull String modelDescription) { * @param modelNumber ModelNumber * @return Builder */ + @Nonnull public Builder setModelNumber(@Nonnull String modelNumber) { mModelNumber = modelNumber; return this; @@ -215,6 +243,7 @@ public Builder setModelNumber(@Nonnull String modelNumber) { * @param serialNumber SerialNumber * @return Builder */ + @Nonnull public Builder setSerialNumber(@Nonnull String serialNumber) { mSerialNumber = serialNumber; return this; @@ -226,6 +255,7 @@ public Builder setSerialNumber(@Nonnull String serialNumber) { * @param presentationUrl PresentationUrl * @return Builder */ + @Nonnull public Builder setPresentationUrl(@Nonnull String presentationUrl) { mPresentationUrl = presentationUrl; return this; @@ -237,6 +267,7 @@ public Builder setPresentationUrl(@Nonnull String presentationUrl) { * @param iconBuilderList 全IconのBuilder * @return Builder */ + @Nonnull public Builder setIconBuilderList(@Nonnull List iconBuilderList) { mIconBuilderList = iconBuilderList; return this; @@ -248,6 +279,7 @@ public Builder setIconBuilderList(@Nonnull List iconBuilderList) { * @param serviceBuilderList 全ServiceのBuilder * @return Builder */ + @Nonnull public Builder setServiceBuilderList(@Nonnull List serviceBuilderList) { mServiceBuilderList = serviceBuilderList; return this; @@ -258,6 +290,7 @@ public Builder setServiceBuilderList(@Nonnull List serviceBuild * * @return 全ServiceのBuilder */ + @Nonnull public List getServiceBuilderList() { return mServiceBuilderList; } @@ -273,6 +306,7 @@ public List getServiceBuilderList() { * @param value タグの値 * @return Builder */ + @Nonnull public Builder putTag(@Nonnull String namespace, @Nonnull String tag, @Nonnull String value) { Map map = mTagMap.get(namespace); if (map == null) { @@ -288,6 +322,7 @@ public Builder putTag(@Nonnull String namespace, @Nonnull String tag, @Nonnull S * * @return Deviceのインスタンス */ + @Nonnull public Device build() { if (mDescription == null) { throw new IllegalStateException("description must be set."); @@ -319,6 +354,8 @@ public Device build() { @Nonnull private SsdpMessage mSsdpMessage; @Nonnull + private String mLocation; + @Nonnull private final String mDescription; @Nonnull private final String mUdn; @@ -350,6 +387,7 @@ public Device build() { private Device(@Nonnull Builder builder) { mControlPoint = builder.mControlPoint; mSsdpMessage = builder.mSsdpMessage; + mLocation = builder.mLocation; mUdn = builder.mUdn; mDeviceType = builder.mDeviceType; mFriendlyName = builder.mFriendlyName; @@ -363,7 +401,7 @@ private Device(@Nonnull Builder builder) { mPresentationUrl = builder.mPresentationUrl; mDescription = builder.mDescription; mTagMap = builder.mTagMap; - if (builder.mIconBuilderList == null) { + if (builder.mIconBuilderList.isEmpty()) { mIconList = Collections.emptyList(); } else { mIconList = new ArrayList<>(builder.mIconBuilderList.size()); @@ -371,7 +409,7 @@ private Device(@Nonnull Builder builder) { mIconList.add(iconBuilder.setDevice(this).build()); } } - if (builder.mServiceBuilderList == null) { + if (builder.mServiceBuilderList.isEmpty()) { mServiceList = Collections.emptyList(); } else { mServiceList = new ArrayList<>(builder.mServiceBuilderList.size()); @@ -423,6 +461,11 @@ void updateSsdpMessage(@Nonnull SsdpMessage message) { if (!getUdn().equals(uuid)) { throw new IllegalArgumentException("uuid and udn does not match! uuid=" + uuid + " udn=" + mUdn); } + final String location = message.getLocation(); + if (location == null) { + throw new IllegalArgumentException(); + } + mLocation = location; mSsdpMessage = message; } @@ -590,8 +633,7 @@ public String getValue(@Nonnull String name, @Nonnull String namespace) { */ @Nonnull public String getLocation() { - //noinspection ConstantConditions : Deviceに設定される場合はnonnullであることが保証されている。 - return mSsdpMessage.getLocation(); + return mLocation; } /** diff --git a/lib/src/main/java/net/mm2d/upnp/HttpClient.java b/lib/src/main/java/net/mm2d/upnp/HttpClient.java index 4b067a9d..3159c735 100644 --- a/lib/src/main/java/net/mm2d/upnp/HttpClient.java +++ b/lib/src/main/java/net/mm2d/upnp/HttpClient.java @@ -162,7 +162,7 @@ private HttpResponse redirectIfNeeded(final @Nonnull HttpRequest request, throws IOException { if (needToRedirect(response) && redirectDepth < REDIRECT_MAX) { final String location = response.getHeader(Http.LOCATION); - if (TextUtils.isEmpty(location)) { + if (!TextUtils.isEmpty(location)) { return redirect(request, location, redirectDepth); } } diff --git a/lib/src/test/java/net/mm2d/upnp/DeviceParserTest.java b/lib/src/test/java/net/mm2d/upnp/DeviceParserTest.java new file mode 100644 index 00000000..0c850774 --- /dev/null +++ b/lib/src/test/java/net/mm2d/upnp/DeviceParserTest.java @@ -0,0 +1,88 @@ +/* + * Copyright(C) 2017 大前良介(OHMAE Ryosuke) + * + * This software is released under the MIT License. + * http://opensource.org/licenses/MIT + */ + +package net.mm2d.upnp; + +import net.mm2d.util.TestUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.net.InterfaceAddress; +import java.net.URL; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +@RunWith(JUnit4.class) +public class DeviceParserTest { + private HttpClient mHttpClient; + private SsdpMessage mSsdpMessage; + private ControlPoint mControlPoint; + + @Before + public void setUp() throws Exception { + mHttpClient = mock(HttpClient.class); + doReturn(TestUtils.getResourceAsString("cds.xml")) + .when(mHttpClient).downloadString(new URL("http://192.0.2.2:12345/cds.xml")); + doReturn(TestUtils.getResourceAsString("cms.xml")) + .when(mHttpClient).downloadString(new URL("http://192.0.2.2:12345/cms.xml")); + doReturn(TestUtils.getResourceAsString("mmupnp.xml")) + .when(mHttpClient).downloadString(new URL("http://192.0.2.2:12345/mmupnp.xml")); + doReturn(TestUtils.getResourceAsByteArray("icon/icon120.jpg")) + .when(mHttpClient).downloadBinary(new URL("http://192.0.2.2:12345/icon/icon120.jpg")); + doReturn(TestUtils.getResourceAsByteArray("icon/icon48.jpg")) + .when(mHttpClient).downloadBinary(new URL("http://192.0.2.2:12345/icon/icon48.jpg")); + doReturn(TestUtils.getResourceAsByteArray("icon/icon120.png")) + .when(mHttpClient).downloadBinary(new URL("http://192.0.2.2:12345/icon/icon120.png")); + doReturn(TestUtils.getResourceAsByteArray("icon/icon48.png")) + .when(mHttpClient).downloadBinary(new URL("http://192.0.2.2:12345/icon/icon48.png")); + final byte[] data = TestUtils.getResourceAsByteArray("ssdp-notify-alive0.bin"); + final InterfaceAddress interfaceAddress = mock(InterfaceAddress.class); + mSsdpMessage = new SsdpRequestMessage(interfaceAddress, data, data.length); + mControlPoint = mock(ControlPoint.class); + } + + @Test + public void loadDescription1() throws Exception { + doReturn(TestUtils.getResourceAsString("device.xml")) + .when(mHttpClient).downloadString(new URL("http://192.0.2.2:12345/device.xml")); + + final Device.Builder builder = new Device.Builder(mControlPoint, mSsdpMessage); + DeviceParser.loadDescription(mHttpClient, builder); + final Device device = builder.build(); + assertThat(device.getIconList(), hasSize(4)); + assertThat(device.getServiceList(), hasSize(3)); + } + + @Test + public void loadDescription_no_icon_device() throws Exception { + doReturn(TestUtils.getResourceAsString("device-no-icon.xml")) + .when(mHttpClient).downloadString(new URL("http://192.0.2.2:12345/device.xml")); + + final Device.Builder builder = new Device.Builder(mControlPoint, mSsdpMessage); + DeviceParser.loadDescription(mHttpClient, builder); + final Device device = builder.build(); + assertThat(device.getIconList(), hasSize(0)); + assertThat(device.getServiceList(), hasSize(3)); + } + + @Test + public void loadDescription_no_service_device() throws Exception { + doReturn(TestUtils.getResourceAsString("device-no-service.xml")) + .when(mHttpClient).downloadString(new URL("http://192.0.2.2:12345/device.xml")); + + final Device.Builder builder = new Device.Builder(mControlPoint, mSsdpMessage); + DeviceParser.loadDescription(mHttpClient, builder); + final Device device = builder.build(); + assertThat(device.getIconList(), hasSize(4)); + assertThat(device.getServiceList(), hasSize(0)); + } +} diff --git a/lib/src/test/resources/device-no-icon.xml b/lib/src/test/resources/device-no-icon.xml new file mode 100644 index 00000000..377e4278 --- /dev/null +++ b/lib/src/test/resources/device-no-icon.xml @@ -0,0 +1,43 @@ + + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaServer:1 + mmupnp + mm2d.net + http://www.mm2d.net/ + mmupnp + mmupnp test server + http://www.mm2d.net/ + ABCDEFG + 0123456789ABC + uuid:01234567-89ab-cdef-0123-456789abcdef + http://192.0.2.2:12346/ + + + urn:schemas-upnp-org:service:ConnectionManager:1 + urn:upnp-org:serviceId:ConnectionManager + /cms.xml + /cms/control + /cms/event + + + urn:schemas-upnp-org:service:ContentDirectory:1 + urn:upnp-org:serviceId:ContentDirectory + /cds.xml + /cds/control + /cds/event + + + urn:schemas-mm2d-net:service:X_mmupnp:1 + urn:upnp-org:serviceId:X_mmupnp + /mmupnp.xml + /mmupnp/control + /mmupnp/event + + + + diff --git a/lib/src/test/resources/device-no-service.xml b/lib/src/test/resources/device-no-service.xml new file mode 100644 index 00000000..2161ec4a --- /dev/null +++ b/lib/src/test/resources/device-no-service.xml @@ -0,0 +1,50 @@ + + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaServer:1 + mmupnp + mm2d.net + http://www.mm2d.net/ + mmupnp + mmupnp test server + http://www.mm2d.net/ + ABCDEFG + 0123456789ABC + uuid:01234567-89ab-cdef-0123-456789abcdef + http://192.0.2.2:12346/ + + + image/jpeg + 120 + 120 + 24 + /icon/icon120.jpg + + + image/jpeg + 48 + 48 + 24 + /icon/icon48.jpg + + + image/png + 120 + 120 + 24 + /icon/icon120.png + + + image/png + 48 + 48 + 24 + /icon/icon48.png + + + +