/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2012-2015 the original author or authors.
*/
package org.assertj.swing.core;
import static java.lang.System.lineSeparator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Preconditions.checkNotNull;
import static org.assertj.core.util.Strings.concat;
import static org.assertj.swing.edt.GuiActionRunner.execute;
import static org.assertj.swing.format.Formatting.format;
import static org.assertj.swing.hierarchy.NewHierarchy.ignoreExistingComponents;
import java.awt.Component;
import java.awt.Container;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Collection;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.JLabel;
import org.assertj.swing.annotation.RunsInEDT;
import org.assertj.swing.exception.ComponentLookupException;
import org.assertj.swing.hierarchy.ComponentHierarchy;
import org.assertj.swing.hierarchy.ExistingHierarchy;
import org.assertj.swing.hierarchy.SingleComponentHierarchy;
/**
* Default implementation of {@link ComponentFinder}.
*
* @author Alex Ruiz
*
* @see ComponentFinder
*/
public final class BasicComponentFinder implements ComponentFinder {
private final ComponentHierarchy hierarchy;
private final ComponentPrinter printer;
private final Settings settings;
private final FinderDelegate finderDelegate = new FinderDelegate();
private boolean includeHierarchyInComponentLookupException;
/**
* Creates a new {@link BasicComponentFinder} with a new AWT hierarchy. AWT and Swing {@code Component}s created
* before the created {@link BasicComponentFinder} cannot be accessed by the created {@link BasicComponentFinder}.
*
* @return the created finder.
*/
public static @Nonnull ComponentFinder finderWithNewAwtHierarchy() {
return new BasicComponentFinder(ignoreExistingComponents());
}
/**
* Creates a new {@link BasicComponentFinder} that has access to all the AWT and Swing {@code Component}s in the AWT
* hierarchy.
*
* @return the created finder.
*/
public static @Nonnull ComponentFinder finderWithCurrentAwtHierarchy() {
return new BasicComponentFinder(new ExistingHierarchy());
}
/**
* Creates a new {@link BasicComponentFinder}. The created finder does not use any {@link Settings}.
*
* @param hierarchy the component hierarchy to use.
*/
protected BasicComponentFinder(@Nonnull ComponentHierarchy hierarchy) {
this(hierarchy, null);
}
/**
* Creates a new {@link BasicComponentFinder}.
*
* @param hierarchy the component hierarchy to use.
* @param settings the configuration settings to use. It can be {@code null}.
*/
protected BasicComponentFinder(@Nonnull ComponentHierarchy hierarchy, @Nullable Settings settings) {
this.hierarchy = hierarchy;
this.settings = settings;
printer = new BasicComponentPrinter(hierarchy);
includeHierarchyIfComponentNotFound(true);
}
@Override
public @Nonnull ComponentPrinter printer() {
return printer;
}
@Override
public @Nonnull <T extends Component> T findByType(@Nonnull Class<T> type) {
return findByType(type, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByType(@Nonnull Class<T> type, boolean showing) {
return type.cast(find(new TypeMatcher(type, showing)));
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByType(@Nonnull Container root, @Nonnull Class<T> type) {
return findByType(root, type, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByType(@Nonnull Container root, @Nonnull Class<T> type, boolean showing) {
return type.cast(find(root, new TypeMatcher(type, showing)));
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByName(@Nullable String name, @Nonnull Class<T> type) {
return findByName(name, type, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByName(@Nullable String name, @Nonnull Class<T> type, boolean showing) {
Component found = find(new NameMatcher(name, type, showing));
return type.cast(found);
}
@RunsInEDT
@Override
public @Nonnull Component findByName(@Nullable String name) {
return findByName(name, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull Component findByName(@Nullable String name, boolean showing) {
return find(new NameMatcher(name, showing));
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByLabel(@Nullable String label, @Nonnull Class<T> type) {
return findByLabel(label, type, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByLabel(@Nullable String label, @Nonnull Class<T> type, boolean showing) {
Component found = find(new LabelMatcher(label, type, showing));
return labelFor(found, type);
}
@RunsInEDT
@Override
public @Nonnull Component findByLabel(@Nullable String label) {
return findByLabel(label, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull Component findByLabel(@Nullable String label, boolean showing) {
Component found = find(new LabelMatcher(label, showing));
return labelFor(found, Component.class);
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T find(@Nonnull GenericTypeMatcher<T> m) {
Component found = find((ComponentMatcher) m);
return m.supportedType().cast(found);
}
@RunsInEDT
@Override
public @Nonnull Component find(@Nonnull ComponentMatcher m) {
return find(hierarchy, m);
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByName(@Nonnull Container root, @Nullable String name,
@Nonnull Class<T> type) {
return findByName(root, name, type, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByName(@Nonnull Container root, @Nullable String name,
@Nonnull Class<T> type, boolean showing) {
Component found = find(root, new NameMatcher(name, type, showing));
return type.cast(found);
}
@RunsInEDT
@Override
public @Nonnull Component findByName(@Nonnull Container root, @Nullable String name) {
return findByName(root, name, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull Component findByName(@Nonnull Container root, @Nullable String name, boolean showing) {
return find(root, new NameMatcher(name, showing));
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByLabel(@Nonnull Container root, @Nullable String label,
@Nonnull Class<T> type) {
return findByLabel(root, label, type, requireShowing());
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T findByLabel(@Nonnull Container root, @Nullable String label,
@Nonnull Class<T> type, boolean showing) {
Component found = find(root, new LabelMatcher(label, type, showing));
return labelFor(found, type);
}
@RunsInEDT
@Override
public @Nonnull Component findByLabel(@Nonnull Container root, @Nullable String label) {
return findByLabel(root, label, requireShowing());
}
private boolean requireShowing() {
return requireShowingFromSettingsOr(false);
}
@RunsInEDT
@Override
public @Nonnull Component findByLabel(@Nonnull Container root, @Nullable String label, boolean showing) {
Component found = find(root, new LabelMatcher(label, showing));
return labelFor(found, Component.class);
}
private @Nonnull <T> T labelFor(@Nonnull Component label, @Nonnull Class<T> type) {
assertThat(label).isInstanceOf(JLabel.class);
Component target = ((JLabel) label).getLabelFor();
assertThat(target).isInstanceOf(type);
return type.cast(target);
}
@RunsInEDT
@Override
public @Nonnull <T extends Component> T find(@Nonnull Container root, @Nonnull GenericTypeMatcher<T> m) {
Component found = find(root, (ComponentMatcher) m);
return m.supportedType().cast(found);
}
@RunsInEDT
@Override
public @Nonnull Component find(@Nullable Container root, @Nonnull ComponentMatcher m) {
return find(hierarchy(root), m);
}
@RunsInEDT
private @Nonnull Component find(@Nonnull ComponentHierarchy h, @Nonnull ComponentMatcher m) {
Collection<Component> found = finderDelegate.find(h, m);
if (found.isEmpty()) {
throw componentNotFound(h, m);
}
if (found.size() > 1) {
throw multipleComponentsFound(found, m);
}
return checkNotNull(found.iterator().next());
}
@RunsInEDT
private @Nonnull ComponentLookupException componentNotFound(@Nonnull ComponentHierarchy h, @Nonnull ComponentMatcher m) {
String message = concat("Unable to find component using matcher ", m, ".");
if (includeHierarchyIfComponentNotFound()) {
message = concat(message, lineSeparator(), lineSeparator(), "Component hierarchy:", lineSeparator(),
formattedHierarchy(root(h)));
}
throw new ComponentLookupException(message);
}
private static @Nullable Container root(@Nullable ComponentHierarchy h) {
if (h instanceof SingleComponentHierarchy) {
return ((SingleComponentHierarchy) h).root();
}
return null;
}
@RunsInEDT
private @Nonnull String formattedHierarchy(@Nullable Container root) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(out, true);
printer.printComponents(printStream, root);
printStream.flush();
return new String(out.toByteArray());
}
@RunsInEDT
private static @Nonnull ComponentLookupException multipleComponentsFound(@Nonnull Collection<Component> found,
@Nonnull ComponentMatcher m) {
StringBuilder message = new StringBuilder();
String format = "Found more than one component using matcher %s. %n%nFound:";
message.append(String.format(format, m.toString()));
appendComponents(message, found);
if (!found.isEmpty()) {
message.append(lineSeparator());
}
throw new ComponentLookupException(message.toString(), found);
}
@RunsInEDT
private static void appendComponents(final @Nonnull StringBuilder message, final @Nonnull Collection<Component> found) {
execute(() -> {
for (Component c : found) {
message.append(String.format("%n%s", format(c)));
}
});
}
@Override
public boolean includeHierarchyIfComponentNotFound() {
return includeHierarchyInComponentLookupException;
}
@Override
public void includeHierarchyIfComponentNotFound(boolean newValue) {
includeHierarchyInComponentLookupException = newValue;
}
@Override
public @Nonnull Collection<Component> findAll(@Nonnull ComponentMatcher m) {
return finderDelegate.find(hierarchy, m);
}
@Override
public @Nonnull Collection<Component> findAll(@Nonnull Container root, @Nonnull ComponentMatcher m) {
return finderDelegate.find(hierarchy(root), m);
}
@Override
public @Nonnull <T extends Component> Collection<T> findAll(@Nonnull GenericTypeMatcher<T> m) {
return finderDelegate.find(hierarchy, m);
}
@Override
public @Nonnull <T extends Component> Collection<T> findAll(@Nonnull Container root, @Nonnull GenericTypeMatcher<T> m) {
ComponentHierarchy h = hierarchy(root);
return finderDelegate.find(h, m);
}
/**
* Returns the value of the flag "requireShowing" in the {@link ComponentLookupScope} this finder's {@link Settings}.
* If the settings object is {@code null}, this method will return the provided default value.
*
* @param defaultValue the value to return if this matcher does not have any configuration settings.
* @return the value of the flag "requireShowing" in this finder's settings, or the provided default value if this
* finder does not have configuration settings.
*/
protected final boolean requireShowingFromSettingsOr(boolean defaultValue) {
if (settings == null) {
return defaultValue;
}
return settings.componentLookupScope().requireShowing();
}
private @Nonnull ComponentHierarchy hierarchy(@Nullable Container root) {
if (root == null) {
return hierarchy;
}
return new SingleComponentHierarchy(root, hierarchy);
}
}