1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.apps.common.testing.ui.espresso; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.android.apps.common.testing.ui.espresso.util.HumanReadables; 22 import com.google.common.collect.ImmutableSet; 23 import com.google.common.collect.Lists; 24 25 import android.view.View; 26 27 import org.hamcrest.Matcher; 28 29 /** 30 * An exception which indicates that a Matcher<View> matched multiple views in the hierarchy when 31 * only one view was expected. It should be called only from the main thread. 32 * <p> 33 * Contains details about the matcher and the current view hierarchy to aid in debugging. 34 * </p> 35 * <p> 36 * Since this is usually an unrecoverable error this exception is a runtime exception. 37 * </p> 38 * <p> 39 * References to the view and failing matcher are purposefully not included in the state of this 40 * object - since it will most likely be created on the UI thread and thrown on the instrumentation 41 * thread, it would be invalid to touch the view on the instrumentation thread. Also the view 42 * hierarchy may have changed since exception creation (leading to more confusion). 43 * </p> 44 */ 45 public final class AmbiguousViewMatcherException extends RuntimeException 46 implements EspressoException { 47 48 private Matcher<? super View> viewMatcher; 49 private View rootView; 50 private View view1; 51 private View view2; 52 private View[] others; 53 54 private AmbiguousViewMatcherException(String description) { 55 super(description); 56 } 57 58 private AmbiguousViewMatcherException(Builder builder) { 59 super(getErrorMessage(builder)); 60 this.viewMatcher = builder.viewMatcher; 61 this.rootView = builder.rootView; 62 this.view1 = builder.view1; 63 this.view2 = builder.view2; 64 this.others = builder.others; 65 } 66 67 private static String getErrorMessage(Builder builder) { 68 String errorMessage = ""; 69 if (builder.includeViewHierarchy) { 70 ImmutableSet<View> ambiguousViews = 71 ImmutableSet.<View>builder().add(builder.view1, builder.view2).add(builder.others).build(); 72 errorMessage = HumanReadables.getViewHierarchyErrorMessage(builder.rootView, 73 Lists.newArrayList(ambiguousViews), 74 String.format("'%s' matches multiple views in the hierarchy.", builder.viewMatcher), 75 "****MATCHES****"); 76 } else { 77 errorMessage = String.format("Multiple Ambiguous Views found for matcher %s", 78 builder.viewMatcher); 79 } 80 return errorMessage; 81 } 82 83 /** Builder for {@link AmbiguousViewMatcherException}. */ 84 public static class Builder { 85 private Matcher<? super View> viewMatcher; 86 private View rootView; 87 private View view1; 88 private View view2; 89 private View[] others; 90 private boolean includeViewHierarchy = true; 91 92 public Builder from(AmbiguousViewMatcherException exception) { 93 this.viewMatcher = exception.viewMatcher; 94 this.rootView = exception.rootView; 95 this.view1 = exception.view1; 96 this.view2 = exception.view2; 97 this.others = exception.others; 98 return this; 99 } 100 101 public Builder withViewMatcher(Matcher<? super View> viewMatcher) { 102 this.viewMatcher = viewMatcher; 103 return this; 104 } 105 106 public Builder withRootView(View rootView) { 107 this.rootView = rootView; 108 return this; 109 } 110 111 public Builder withView1(View view1) { 112 this.view1 = view1; 113 return this; 114 } 115 116 public Builder withView2(View view2) { 117 this.view2 = view2; 118 return this; 119 } 120 121 public Builder withOtherAmbiguousViews(View... others) { 122 this.others = others; 123 return this; 124 } 125 126 public Builder includeViewHierarchy(boolean includeViewHierarchy) { 127 this.includeViewHierarchy = includeViewHierarchy; 128 return this; 129 } 130 131 public AmbiguousViewMatcherException build() { 132 checkNotNull(viewMatcher); 133 checkNotNull(rootView); 134 checkNotNull(view1); 135 checkNotNull(view2); 136 checkNotNull(others); 137 return new AmbiguousViewMatcherException(this); 138 } 139 } 140 } 141