Skip to content

Commit

Permalink
Serviceの存在しないDeviceをロードするとNullPointerExceptionが発生する問題の修正
Browse files Browse the repository at this point in the history
  • Loading branch information
ohmae committed Mar 18, 2017
1 parent 1d7203c commit 39f563a
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 8 deletions.
56 changes: 49 additions & 7 deletions lib/src/main/java/net/mm2d/upnp/Device.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -52,8 +55,10 @@ public static class Builder {
private String mModelNumber;
private String mSerialNumber;
private String mPresentationUrl;
private List<Icon.Builder> mIconBuilderList;
private List<Service.Builder> mServiceBuilderList;
@Nonnull
private List<Icon.Builder> mIconBuilderList = Collections.emptyList();
@Nonnull
private List<Service.Builder> mServiceBuilderList = Collections.emptyList();
@Nonnull
private final Map<String, Map<String, String>> mTagMap;

Expand All @@ -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<String, String>());
Expand All @@ -75,15 +85,17 @@ public Builder(@Nonnull ControlPoint controlPoint, @Nonnull SsdpMessage ssdpMess
*
* @return SSDPに記述されたLocationの値
*/
@Nonnull
public String getLocation() {
return mSsdpMessage.getLocation();
return mLocation;
}

/**
* SSDPに記述されたUUIDを返す。
*
* @return SSDPに記述されたUUID
*/
@Nonnull
public String getUuid() {
return mSsdpMessage.getUuid();
}
Expand All @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -237,6 +267,7 @@ public Builder setPresentationUrl(@Nonnull String presentationUrl) {
* @param iconBuilderList 全IconのBuilder
* @return Builder
*/
@Nonnull
public Builder setIconBuilderList(@Nonnull List<Icon.Builder> iconBuilderList) {
mIconBuilderList = iconBuilderList;
return this;
Expand All @@ -248,6 +279,7 @@ public Builder setIconBuilderList(@Nonnull List<Icon.Builder> iconBuilderList) {
* @param serviceBuilderList 全ServiceのBuilder
* @return Builder
*/
@Nonnull
public Builder setServiceBuilderList(@Nonnull List<Service.Builder> serviceBuilderList) {
mServiceBuilderList = serviceBuilderList;
return this;
Expand All @@ -258,6 +290,7 @@ public Builder setServiceBuilderList(@Nonnull List<Service.Builder> serviceBuild
*
* @return 全ServiceのBuilder
*/
@Nonnull
public List<Service.Builder> getServiceBuilderList() {
return mServiceBuilderList;
}
Expand All @@ -273,6 +306,7 @@ public List<Service.Builder> getServiceBuilderList() {
* @param value タグの値
* @return Builder
*/
@Nonnull
public Builder putTag(@Nonnull String namespace, @Nonnull String tag, @Nonnull String value) {
Map<String, String> map = mTagMap.get(namespace);
if (map == null) {
Expand All @@ -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.");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -363,15 +401,15 @@ 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());
for (Icon.Builder iconBuilder : builder.mIconBuilderList) {
mIconList.add(iconBuilder.setDevice(this).build());
}
}
if (builder.mServiceBuilderList == null) {
if (builder.mServiceBuilderList.isEmpty()) {
mServiceList = Collections.emptyList();
} else {
mServiceList = new ArrayList<>(builder.mServiceBuilderList.size());
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/src/main/java/net/mm2d/upnp/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
88 changes: 88 additions & 0 deletions lib/src/test/java/net/mm2d/upnp/DeviceParserTest.java
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading

0 comments on commit 39f563a

Please sign in to comment.