/** * 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); } }