content: /*
   * Created on May 14, 2007
   *
   * 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 @2007-2013 the original author or authors.
   */
  package org.fest.swing.core;
  
  import static org.fest.assertions.Assertions.assertThat;
  import static org.fest.swing.edt.GuiActionRunner.execute;
  import static org.fest.swing.format.Formatting.format;
  import static org.fest.swing.hierarchy.NewHierarchy.ignoreExistingComponents;
  import static org.fest.util.Preconditions.checkNotNull;
  import static org.fest.util.Strings.concat;
  import static org.fest.util.SystemProperties.lineSeparator;
  
  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.fest.swing.annotation.RunsInEDT;
  import org.fest.swing.edt.GuiTask;
  import org.fest.swing.exception.ComponentLookupException;
  import org.fest.swing.hierarchy.ComponentHierarchy;
  import org.fest.swing.hierarchy.ExistingHierarchy;
  import org.fest.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);
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull ComponentPrinter printer() {
      return printer;
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull <T extends Component> T findByType(@Nonnull Class<T> type) {
      return findByType(type, requireShowing());
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull <T extends Component> T findByType(@Nonnull Class<T> type, boolean showing) {
      return type.cast(find(new TypeMatcher(type, showing)));
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull <T extends Component> T findByType(@Nonnull Container root, @Nonnull Class<T> type) {
      return findByType(root, type, requireShowing());
    }
  
    /** {@inheritDoc} */
    @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)));
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull <T extends Component> T findByName(@Nullable String name, @Nonnull Class<T> type) {
      return findByName(name, type, requireShowing());
    }
  
    /** {@inheritDoc} */
    @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);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component findByName(@Nullable String name) {
      return findByName(name, requireShowing());
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component findByName(@Nullable String name, boolean showing) {
      return find(new NameMatcher(name, showing));
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull <T extends Component> T findByLabel(@Nullable String label, @Nonnull Class<T> type) {
      return findByLabel(label, type, requireShowing());
    }
  
    /** {@inheritDoc} */
    @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);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component findByLabel(@Nullable String label) {
      return findByLabel(label, requireShowing());
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component findByLabel(@Nullable String label, boolean showing) {
      Component found = find(new LabelMatcher(label, showing));
      return labelFor(found, Component.class);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull <T extends Component> T find(@Nonnull GenericTypeMatcher<T> m) {
      Component found = find((ComponentMatcher) m);
      return m.supportedType().cast(found);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component find(@Nonnull ComponentMatcher m) {
      return find(hierarchy, m);
    }
  
    /** {@inheritDoc} */
    @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());
    }
  
    /** {@inheritDoc} */
    @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);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component findByName(@Nonnull Container root, @Nullable String name) {
      return findByName(root, name, requireShowing());
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component findByName(@Nonnull Container root, @Nullable String name, boolean showing) {
      return find(root, new NameMatcher(name, showing));
    }
  
    /** {@inheritDoc} */
    @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());
    }
  
    /** {@inheritDoc} */
    @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);
    }
  
    /** {@inheritDoc} */
    @RunsInEDT
    @Override
    public @Nonnull Component findByLabel(@Nonnull Container root, @Nullable String label) {
      return findByLabel(root, label, requireShowing());
    }
  
    private boolean requireShowing() {
      return requireShowingFromSettingsOr(false);
    }
  
    /** {@inheritDoc} */
    @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);
    }
  
    /** {@inheritDoc} */
    @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);
    }
  
    /** {@inheritDoc} */
    @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(new GuiTask() {
        @Override
        protected void executeInEDT() {
          for (Component c : found) {
            message.append(String.format("%n%s", format(c)));
          }
        }
      });
    }
  
    /** {@inheritDoc} */
    @Override
    public boolean includeHierarchyIfComponentNotFound() {
      return includeHierarchyInComponentLookupException;
    }
  
    /** {@inheritDoc} */
    @Override
    public void includeHierarchyIfComponentNotFound(boolean newValue) {
      includeHierarchyInComponentLookupException = newValue;
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull Collection<Component> findAll(@Nonnull ComponentMatcher m) {
      return finderDelegate.find(hierarchy, m);
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull Collection<Component> findAll(@Nonnull Container root, @Nonnull ComponentMatcher m) {
      return finderDelegate.find(hierarchy(root), m);
    }
  
    /** {@inheritDoc} */
    @Override
    public @Nonnull <T extends Component> Collection<T> findAll(@Nonnull GenericTypeMatcher<T> m) {
      return finderDelegate.find(hierarchy, m);
    }
  
    /** {@inheritDoc} */
    @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);
    }
  }
  