Advanced Graphics Java

/*
 * Copyright (c) 2007, Romain Guy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the TimingFramework project nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
 * @author Romain Guy
 */
public class BackgroundAnimation_2008 extends JFrame {
  private AvatarChooser chooser;
  private CurvesPanel curves;
  public BackgroundAnimation_2008() throws HeadlessException {
    super("Stack Layout");
    buildContentPane();
    // buildDebugControls();
    startAnimation();
    setSize(640, 400);
    setLocationRelativeTo(null);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
  }
  private void startAnimation() {
    Timer timer = new Timer(50, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        curves.animate();
        curves.repaint();
      }
    });
    timer.start();
  }
  private void buildDebugControls() {
    JPanel pane = new JPanel(new BorderLayout());
    pane.setBackground(Color.WHITE);
    pane.add(new JSeparator(), BorderLayout.NORTH);
    final GraphPanel grapher = new GraphPanel();
    JPanel graphPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
    graphPane.setOpaque(false);
    graphPane.add(grapher);
    JPanel buttonsPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
    buttonsPane.setOpaque(false);
    JSlider spacing = new JSlider(JSlider.HORIZONTAL, 0, 100, 40);
    spacing.setBackground(Color.WHITE);
    spacing.setPreferredSize(new Dimension(95, spacing.getPreferredSize().height));
    spacing.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        JSlider slider = (JSlider) e.getSource();
        double spacing = slider.getValue() / 100.0;
        chooser.setSpacing(spacing);
        grapher.spacing = spacing;
        grapher.repaint();
      }
    });
    JSlider sigma = new JSlider(JSlider.HORIZONTAL, 0, 100, 50);
    sigma.setBackground(Color.WHITE);
    sigma.setPreferredSize(new Dimension(95, sigma.getPreferredSize().height));
    sigma.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        JSlider slider = (JSlider) e.getSource();
        double sigma = slider.getValue() / 100.0;
        chooser.setSigma(sigma);
        grapher.sigma = sigma;
        grapher.repaint();
      }
    });
    JSlider position = new JSlider(JSlider.HORIZONTAL, -100, 100, 0);
    position.setBackground(Color.WHITE);
    position.setPreferredSize(new Dimension(95, position.getPreferredSize().height));
    position.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        JSlider slider = (JSlider) e.getSource();
        double position = slider.getValue() / 100.0;
        chooser.setPosition(position);
        grapher.position = position;
        grapher.repaint();
      }
    });
    JSlider amount = new JSlider(JSlider.HORIZONTAL, 1, 15, 5);
    amount.setBackground(Color.WHITE);
    amount.setPreferredSize(new Dimension(95, position.getPreferredSize().height));
    amount.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        JSlider slider = (JSlider) e.getSource();
        int amount = slider.getValue();
        chooser.setAmount(amount);
        grapher.amount = amount;
        grapher.repaint();
      }
    });
    buttonsPane.add(new JLabel("Spacing: "));
    buttonsPane.add(spacing);
    buttonsPane.add(new JLabel("Sigma: "));
    buttonsPane.add(sigma);
    buttonsPane.add(new JLabel("Position: "));
    buttonsPane.add(position);
    buttonsPane.add(new JLabel("Amount: "));
    buttonsPane.add(amount);
    pane.add(buttonsPane, BorderLayout.NORTH);
    pane.add(graphPane, BorderLayout.CENTER);
    add(pane, BorderLayout.SOUTH);
  }
  private void buildContentPane() {
    JPanel pane = new JPanel();
    pane.setLayout(new StackLayout());
    GradientPanel gradient = new GradientPanel();
    chooser = new AvatarChooser();
    curves = new CurvesPanel();
    pane.add(gradient, StackLayout.TOP);
    pane.add(chooser, StackLayout.TOP);
    pane.add(curves, StackLayout.TOP);
    add(pane);
  }
  private class GraphPanel extends JComponent {
    private double spacing = 0.4;
    private double position = 0.0;
    private double sigma = 0.5;
    private int amount = 5;
    @Override
    public Dimension getPreferredSize() {
      return new Dimension(200, 60);
    }
    @Override
    public boolean isOpaque() {
      return false;
    }
    @Override
    protected void paintComponent(Graphics g) {
      Graphics2D g2 = (Graphics2D) g;
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setColor(Color.BLACK);
      g2.drawLine(0, 50, 100, 50);
      g2.drawLine(50, 0, 50, 60);
      g2.setColor(Color.BLUE);
      double lastY = 50.0;
      for (int x = 0; x < 100; x++) {
        double y = chooser.computeModifier((50.0 - x) / 25.0) * 45.0 + 10;
        if (x == 0) {
          lastY = y;
        }
        g2.drawLine(x - 1, 60 - (int) lastY, x, 60 - (int) y);
        lastY = y;
      }
      g2.setColor(Color.RED);
      for (int i = 0; i < amount; i++) {
        double offset = ((amount / 2) - i) * spacing;
        double x = (100.0 - 5.0) / 2.0;
        x += 25.0 * (position + offset);
        if (x > 100) {
          continue;
        }
        double y = 60.0 - (chooser.computeModifier(position + offset) * 45.0 + 10);
        g2.fill(new Rectangle2D.Double(x, y - 1, 5.0, 5.0));
      }
      g2.setColor(Color.GREEN.darker());
      g2.drawLine(25, 0, 25, 60);
      g2.drawLine(75, 0, 75, 60);
      g2.setColor(Color.DARK_GRAY);
      g2.drawString("Sigma: " + sigma, 110.0f, 16.0f);
      g2.drawString("Spacing: " + spacing, 110.0f, 30.0f);
      g2.drawString("Position: " + position, 110.0f, 44.0f);
    }
  }
  public static void main(String[] args) {
    try {
      UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
    } catch (ClassNotFoundException ex) {
      ex.printStackTrace();
    } catch (IllegalAccessException ex) {
      ex.printStackTrace();
    } catch (InstantiationException ex) {
      ex.printStackTrace();
    } catch (UnsupportedLookAndFeelException ex) {
      ex.printStackTrace();
    }
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        BackgroundAnimation_2008 tester = new BackgroundAnimation_2008();
        tester.setVisible(true);
      }
    });
  }
}
/*
 * Copyright (c) 2007, Romain Guy All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. * Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. * Neither the name of the
 * TimingFramework project nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
class AvatarChooser extends JPanel {
  private static final double ANIM_SCROLL_DELAY = 450;
  private List avatars = null;
  private boolean loadingDone = false;
  private Thread picturesFinder = null;
  private Timer scrollerTimer = null;
  private Timer faderTimer = null;
  private Timer passwordTimer;
  private float veilAlphaLevel = 0.0f;
  private float alphaLevel = 0.0f;
  private float textAlphaLevel = 0.0f;
  private int avatarIndex;
  private double avatarPosition = 0.0;
  private double avatarSpacing = 0.4;
  private int avatarAmount = 5;
  private double sigma;
  private double rho;
  private double exp_multiplier;
  private double exp_member;
  private boolean damaged = true;
  private DrawableAvatar[] drawableAvatars;
  private String textAvatar;
  private FocusGrabber focusGrabber;
  private AvatarScroller avatarScroller;
  private CursorChanger cursorChanger;
  private MouseWheelScroller wheelScroller;
  private KeyScroller keyScroller;
  public AvatarChooser() {
    GridBagLayout layout = new GridBagLayout();
    setLayout(layout);
    findAvatars();
    setSigma(0.5);
    addComponentListener(new DamageManager());
    initInputListeners();
    addInputListeners();
  }
  public void setAmount(int amount) {
    if (amount > avatars.size()) {
      throw new IllegalArgumentException("Too many avatars");
    }
    this.avatarAmount = amount;
    repaint();
  }
  // XXX package access for debugging purpose only
  void setPosition(double position) {
    this.avatarPosition = position;
    this.damaged = true;
    repaint();
  }
  public void setSigma(double sigma) {
    this.sigma = sigma;
    this.rho = 1.0;
    computeEquationParts();
    this.rho = computeModifierUnprotected(0.0);
    computeEquationParts();
    this.damaged = true;
    repaint();
  }
  public void setSpacing(double avatarSpacing) {
    if (avatarSpacing < 0.0 || avatarSpacing > 1.0) {
      throw new IllegalArgumentException("Spacing must be < 1.0 and > 0.0");
    }
    this.avatarSpacing = avatarSpacing;
    this.damaged = true;
    repaint();
  }
  @Override
  public Dimension getPreferredSize() {
    return new Dimension(128 * 3, 128 * 2);
  }
  @Override
  public Dimension getMaximumSize() {
    return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  }
  @Override
  public boolean isOpaque() {
    return false;
  }
  @Override
  public boolean isFocusable() {
    return true;
  }
  @Override
  protected void paintChildren(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;
    Composite oldComposite = g2.getComposite();
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, veilAlphaLevel));
    super.paintChildren(g);
    g2.setComposite(oldComposite);
  }
  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (!loadingDone && faderTimer == null) {
      return;
    }
    Insets insets = getInsets();
    int x = insets.left;
    int y = insets.top;
    int width = getWidth() - insets.left - insets.right;
    int height = getHeight() - insets.top - insets.bottom;
    Graphics2D g2 = (Graphics2D) g;
    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    Composite oldComposite = g2.getComposite();
    if (damaged) {
      drawableAvatars = sortAvatarsByDepth(x, y, width, height);
      damaged = false;
    }
    drawAvatars(g2, drawableAvatars);
    if (drawableAvatars.length > 0) {
      drawAvatarName(g2);
    }
    g2.setComposite(oldComposite);
  }
  private void drawAvatars(Graphics2D g2, DrawableAvatar[] drawableAvatars) {
    for (DrawableAvatar avatar : drawableAvatars) {
      AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) avatar
          .getAlpha());
      g2.setComposite(composite);
      g2.drawImage(avatars.get(avatar.getIndex()), (int) avatar.getX(), (int) avatar.getY(), avatar
          .getWidth(), avatar.getHeight(), null);
    }
  }
  private DrawableAvatar[] sortAvatarsByDepth(int x, int y, int width, int height) {
    List drawables = new LinkedList();
    for (int i = 0; i < avatars.size(); i++) {
      promoteAvatarToDrawable(drawables, x, y, width, height, i - avatarIndex);
    }
    DrawableAvatar[] drawableAvatars = new DrawableAvatar[drawables.size()];
    drawableAvatars = drawables.toArray(drawableAvatars);
    Arrays.sort(drawableAvatars);
    return drawableAvatars;
  }
  private void drawAvatarName(Graphics2D g2) {
    Composite composite = g2.getComposite();
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, textAlphaLevel));
    double bulletWidth = 150.0;
    double bulletHeight = 30.0;
    double x = (getWidth() - bulletWidth) / 2.0;
    double y = (getHeight() - 164) / 2.0 - bulletHeight * 1.4;
    drawAvatarBullet(g2, x, y, bulletWidth, bulletHeight);
    drawAvatarText(g2, y, bulletHeight);
    g2.setComposite(composite);
  }
  private void drawAvatarText(Graphics2D g2, double y, double bulletHeight) {
    FontRenderContext context = g2.getFontRenderContext();
    Font font = new Font("Dialog", Font.PLAIN, 18);
    TextLayout layout = new TextLayout(textAvatar, font, context);
    Rectangle2D bounds = layout.getBounds();
    float text_x = (float) ((getWidth() - bounds.getWidth()) / 2.0);
    float text_y = (float) (y + (bulletHeight - layout.getAscent() - layout.getDescent()) / 2.0)
        + layout.getAscent() - layout.getLeading();
    g2.setColor(Color.BLACK);
    layout.draw(g2, text_x, text_y + 1);
    g2.setColor(Color.WHITE);
    layout.draw(g2, text_x, text_y);
  }
  private void drawAvatarBullet(Graphics2D g2, double x, double y, double bulletWidth,
      double bulletHeight) {
    RoundRectangle2D bullet = new RoundRectangle2D.Double(0.0, 0.0, bulletWidth, bulletHeight,
        bulletHeight, bulletHeight);
    Ellipse2D curve = new Ellipse2D.Double(-20.0, bulletHeight / 2.0, bulletWidth + 40.0,
        bulletHeight);
    g2.translate(x, y);
    g2.translate(-1, -2);
    g2.setColor(new Color(0, 0, 0, 170));
    g2.fill(new RoundRectangle2D.Double(0.0, 0.0, bulletWidth + 2, bulletHeight + 4,
        bulletHeight + 4, bulletHeight + 4));
    g2.translate(1, 2);
    Color startColor = new Color(10, 0, 40);
    Color endColor = new Color(175, 165, 225);
    Paint paint = g2.getPaint();
    g2.setPaint(new GradientPaint(0.0f, 0.0f, startColor, 0.0f, (float) bulletHeight, endColor));
    g2.fill(bullet);
    startColor = new Color(5, 0, 50);
    endColor = new Color(105, 100, 155);
    g2.setPaint(new GradientPaint(0.0f, 0.0f, startColor, 0.0f, (float) bulletHeight, endColor));
    Area area = new Area(bullet);
    area.intersect(new Area(curve));
    g2.fill(area);
    g2.setPaint(paint);
    g2.translate(-x, -y);
  }
  private void promoteAvatarToDrawable(List drawables, int x, int y, int width,
      int height, int offset) {
    double spacing = offset * avatarSpacing;
    double avatarPosition = this.avatarPosition + spacing;
    if (avatarIndex + offset < 0 || avatarIndex + offset >= avatars.size()) {
      return;
    }
    Image avatar = avatars.get(avatarIndex + offset);
    int avatarWidth = avatar.getWidth(null);
    int avatarHeight = avatar.getHeight(null);
    double result = computeModifier(avatarPosition);
    int newWidth = (int) (avatarWidth * result);
    if (newWidth == 0) {
      return;
    }
    int newHeight = (int) (avatarHeight * result);
    if (newHeight == 0) {
      return;
    }
    double avatar_x = x + (width - newWidth) / 2.0;
    double avatar_y = y + (height - newHeight / 2.0) / 2.0;
    double semiWidth = width / 2.0;
    avatar_x += avatarPosition * semiWidth;
    if (avatar_x >= width || avatar_x < -newWidth) {
      return;
    }
    drawables.add(new DrawableAvatar(avatarIndex + offset, avatar_x, avatar_y, newWidth, newHeight,
        avatarPosition, result));
  }
  private void startFader() {
    faderTimer = new Timer(35, new FaderAction());
    faderTimer.start();
  }
  private void computeEquationParts() {
    exp_multiplier = Math.sqrt(2.0 * Math.PI) / sigma / rho;
    exp_member = 4.0 * sigma * sigma;
  }
  // XXX package access for debug purpose only
  double computeModifier(double x) {
    double result = computeModifierUnprotected(x);
    if (result > 1.0) {
      result = 1.0;
    } else if (result < -1.0) {
      result = -1.0;
    }
    return result;
  }
  private double computeModifierUnprotected(double x) {
    return exp_multiplier * Math.exp((-x * x) / exp_member);
  }
  private void addInputListeners() {
    addMouseListener(focusGrabber);
    addMouseListener(avatarScroller);
    addMouseMotionListener(cursorChanger);
    addMouseWheelListener(wheelScroller);
    addKeyListener(keyScroller);
  }
  private void initInputListeners() {
    // input listeners are all stateless
    // hence they can be instantiated once
    focusGrabber = new FocusGrabber();
    avatarScroller = new AvatarScroller();
    cursorChanger = new CursorChanger();
    wheelScroller = new MouseWheelScroller();
    keyScroller = new KeyScroller();
  }
  private void removeInputListeners() {
    removeMouseListener(focusGrabber);
    removeMouseListener(avatarScroller);
    removeMouseMotionListener(cursorChanger);
    removeMouseWheelListener(wheelScroller);
    removeKeyListener(keyScroller);
  }
  private void findAvatars() {
    avatars = new ArrayList();
    picturesFinder = new Thread(new PicturesFinderThread());
    picturesFinder.start();
  }
  private void setAvatarIndex(int index) {
    avatarIndex = index;
    textAvatar = "Photo " + index;
  }
  private void scrollBy(int increment) {
    if (loadingDone) {
      setAvatarIndex(avatarIndex + increment);
      if (avatarIndex < 0) {
        setAvatarIndex(0);
      } else if (avatarIndex >= avatars.size()) {
        setAvatarIndex(avatars.size() - 1);
      }
      damaged = true;
      repaint();
    }
  }
  private void scrollAndAnimateBy(int increment) {
    if (loadingDone && (scrollerTimer == null || !scrollerTimer.isRunning())) {
      int index = avatarIndex + increment;
      if (index < 0) {
        index = 0;
      } else if (index >= avatars.size()) {
        index = avatars.size() - 1;
      }
      DrawableAvatar drawable = null;
      for (DrawableAvatar avatar : drawableAvatars) {
        if (avatar.index == index) {
          drawable = avatar;
          break;
        }
      }
      if (drawable != null) {
        scrollAndAnimate(drawable);
      }
    }
  }
  private void scrollAndAnimate(DrawableAvatar avatar) {
    if (loadingDone) {
      scrollerTimer = new Timer(10, new AutoScroller(avatar));
      scrollerTimer.start();
    }
  }
  private BufferedImage createReflectedPicture(BufferedImage avatar) {
    int avatarWidth = avatar.getWidth();
    int avatarHeight = avatar.getHeight();
    BufferedImage alphaMask = createGradientMask(avatarWidth, avatarHeight);
    return createReflectedPicture(avatar, alphaMask);
  }
  private BufferedImage createReflectedPicture(BufferedImage avatar, BufferedImage alphaMask) {
    int avatarWidth = avatar.getWidth();
    int avatarHeight = avatar.getHeight();
    BufferedImage buffer = createReflection(avatar, avatarWidth, avatarHeight);
    applyAlphaMask(buffer, alphaMask, avatarWidth, avatarHeight);
    return buffer;
  }
  private void applyAlphaMask(BufferedImage buffer, BufferedImage alphaMask, int avatarWidth,
      int avatarHeight) {
    Graphics2D g2 = buffer.createGraphics();
    g2.setComposite(AlphaComposite.DstOut);
    g2.drawImage(alphaMask, null, 0, avatarHeight);
    g2.dispose();
  }
  private BufferedImage createReflection(BufferedImage avatar, int avatarWidth, int avatarHeight) {
    BufferedImage buffer = new BufferedImage(avatarWidth, avatarHeight << 1,
        BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = buffer.createGraphics();
    g.drawImage(avatar, null, null);
    g.translate(0, avatarHeight << 1);
    AffineTransform reflectTransform = AffineTransform.getScaleInstance(1.0, -1.0);
    g.drawImage(avatar, reflectTransform, null);
    g.translate(0, -(avatarHeight << 1));
    g.dispose();
    return buffer;
  }
  private BufferedImage createGradientMask(int avatarWidth, int avatarHeight) {
    BufferedImage gradient = new BufferedImage(avatarWidth, avatarHeight,
        BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = gradient.createGraphics();
    GradientPaint painter = new GradientPaint(0.0f, 0.0f, new Color(1.0f, 1.0f, 1.0f, 0.5f), 0.0f,
        avatarHeight / 2.0f, new Color(1.0f, 1.0f, 1.0f, 1.0f));
    g.setPaint(painter);
    g.fill(new Rectangle2D.Double(0, 0, avatarWidth, avatarHeight));
    g.dispose();
    return gradient;
  }
  private DrawableAvatar getHitAvatar(int x, int y) {
    for (DrawableAvatar avatar : drawableAvatars) {
      Rectangle hit = new Rectangle((int) avatar.getX(), (int) avatar.getY(), avatar.getWidth(),
          avatar.getHeight() / 2);
      if (hit.contains(x, y)) {
        return avatar;
      }
    }
    return null;
  }
  private class PicturesFinderThread implements Runnable {
    public void run() {
      UnixGlobFileFilter allPngs = new UnixGlobFileFilter("*.jpg");
      try {
        FileTreeWalker walker = new FileTreeWalker(new File("."), allPngs);
        PictureLoader loader = new PictureLoader();
        walker.walk(loader);
        List files = loader.getFilesList();
        for (int i = 0; i < files.size(); i++) {
          BufferedImage image = ImageIO.read(files.get(i));
          avatars.add(createReflectedPicture(image));
          if (i == (files.size() / 2) + avatarAmount / 2) {
            setAvatarIndex(i - avatarAmount / 2);
            startFader();
          }
        }
      } catch (IOException e) {
      }
      loadingDone = true;
    }
    private class PictureLoader implements FileTreeWalk {
      private List filesList;
      private PictureLoader() {
        filesList = new ArrayList();
      }
      private List getFilesList() {
        Collections.sort(filesList);
        return filesList;
      }
      public void walk(File path) {
        filesList.add(path);
      }
    }
  }
  private class FaderAction implements ActionListener {
    private long start = 0;
    private FaderAction() {
      alphaLevel = 0.0f;
      textAlphaLevel = 0.0f;
    }
    public void actionPerformed(ActionEvent e) {
      if (start == 0) {
        start = System.currentTimeMillis();
      }
      alphaLevel = (System.currentTimeMillis() - start) / 500.0f;
      textAlphaLevel = alphaLevel;
      if (alphaLevel > 1.0f) {
        alphaLevel = 1.0f;
        textAlphaLevel = 1.0f;
        faderTimer.stop();
      }
      repaint();
    }
  }
  private class DrawableAvatar implements Comparable {
    private int index;
    private double x;
    private double y;
    private int width;
    private int height;
    private double zOrder;
    private double position;
    private DrawableAvatar(int index, double x, double y, int width, int height, double position,
        double zOrder) {
      this.index = index;
      this.x = x;
      this.y = y;
      this.width = width;
      this.height = height;
      this.position = position;
      this.zOrder = zOrder;
    }
    public int compareTo(Object o) {
      double zOrder2 = ((DrawableAvatar) o).zOrder;
      if (zOrder < zOrder2) {
        return -1;
      } else if (zOrder > zOrder2) {
        return 1;
      }
      return 0;
    }
    public double getPosition() {
      return position;
    }
    public double getAlpha() {
      return zOrder * alphaLevel;
    }
    public int getHeight() {
      return height;
    }
    public int getIndex() {
      return index;
    }
    public int getWidth() {
      return width;
    }
    public double getX() {
      return x;
    }
    public double getY() {
      return y;
    }
  }
  private class MouseWheelScroller implements MouseWheelListener {
    public void mouseWheelMoved(MouseWheelEvent e) {
      int increment = e.getWheelRotation();
      scrollAndAnimateBy(increment);
    }
  }
  private class KeyScroller extends KeyAdapter {
    @Override
    public void keyPressed(KeyEvent e) {
      int keyCode = e.getKeyCode();
      switch (keyCode) {
      case KeyEvent.VK_LEFT:
      case KeyEvent.VK_UP:
        scrollAndAnimateBy(-1);
        break;
      case KeyEvent.VK_RIGHT:
      case KeyEvent.VK_DOWN:
        scrollAndAnimateBy(1);
        break;
      case KeyEvent.VK_END:
        scrollBy(avatars.size() - avatarIndex - 1);
        break;
      case KeyEvent.VK_HOME:
        scrollBy(-avatarIndex - 1);
        break;
      case KeyEvent.VK_PAGE_UP:
        scrollAndAnimateBy(-avatarAmount / 2);
        break;
      case KeyEvent.VK_PAGE_DOWN:
        scrollAndAnimateBy(avatarAmount / 2);
        break;
      }
    }
  }
  private class FocusGrabber extends MouseAdapter {
    @Override
    public void mouseClicked(MouseEvent e) {
      requestFocus();
    }
  }
  private class AvatarScroller extends MouseAdapter {
    @Override
    public void mouseClicked(MouseEvent e) {
      if ((faderTimer != null && faderTimer.isRunning())
          || (scrollerTimer != null && scrollerTimer.isRunning()) || drawableAvatars == null) {
        return;
      }
      if (e.getButton() == MouseEvent.BUTTON1) {
        DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY());
        if (avatar != null && avatar.getIndex() != avatarIndex) {
          scrollAndAnimate(avatar);
        }
      }
    }
  }
  private class DamageManager extends ComponentAdapter {
    @Override
    public void componentResized(ComponentEvent e) {
      damaged = true;
    }
  }
  private class AutoScroller implements ActionListener {
    private double position;
    private int index;
    private long start;
    private AutoScroller(DrawableAvatar avatar) {
      this.index = avatar.getIndex();
      this.position = avatar.getPosition();
      this.start = System.currentTimeMillis();
    }
    public void actionPerformed(ActionEvent e) {
      long elapsed = System.currentTimeMillis() - start;
      if (elapsed < ANIM_SCROLL_DELAY / 2.0) {
        textAlphaLevel = (float) (1.0 - 2.0 * (elapsed / ANIM_SCROLL_DELAY));
      } else {
        textAlphaLevel = (float) (((elapsed / ANIM_SCROLL_DELAY) - 0.5) * 2.0);
        if (textAlphaLevel > 1.0f) {
          textAlphaLevel = 1.0f;
        }
      }
      if (textAlphaLevel < 0.1f) {
        textAlphaLevel = 0.1f;
        textAvatar = "LoginName" + index;
      }
      double newPosition = (elapsed / ANIM_SCROLL_DELAY) * -position;
      if (elapsed >= ANIM_SCROLL_DELAY) {
        ((Timer) e.getSource()).stop();
        setAvatarIndex(index);
        setPosition(0.0);
        return;
      }
      setPosition(newPosition);
    }
  }
  private class CursorChanger extends MouseMotionAdapter {
    @Override
    public void mouseMoved(MouseEvent e) {
      if ((scrollerTimer != null && scrollerTimer.isRunning()) || drawableAvatars == null) {
        return;
      }
      DrawableAvatar avatar = getHitAvatar(e.getX(), e.getY());
      if (avatar != null) {
        getParent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else {
        getParent().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      }
    }
  }
}
/*
 * Copyright (c) 2007, Romain Guy All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. * Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. * Neither the name of the
 * TimingFramework project nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
class CurvesPanel extends JPanel {
  protected RenderingHints hints;
  protected int counter = 0;
  protected Color start = new Color(255, 255, 255, 200);
  protected Color end = new Color(255, 255, 255, 0);
  public CurvesPanel() {
    this(new BorderLayout());
  }
  public CurvesPanel(LayoutManager manager) {
    super(manager);
    hints = createRenderingHints();
  }
  protected RenderingHints createRenderingHints() {
    RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    return hints;
  }
  public void animate() {
    counter++;
  }
  @Override
  public boolean isOpaque() {
    return false;
  }
  @Override
  protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;
    RenderingHints oldHints = g2.getRenderingHints();
    g2.setRenderingHints(hints);
    float width = getWidth();
    float height = getHeight();
    g2.translate(0, -30);
    drawCurve(g2, 20.0f, -10.0f, 20.0f, -10.0f, width / 2.0f - 40.0f, 10.0f, 0.0f, -5.0f,
        width / 2.0f + 40, 1.0f, 0.0f, 5.0f, 50.0f, 5.0f, false);
    g2.translate(0, 30);
    g2.translate(0, height - 60);
    drawCurve(g2, 30.0f, -15.0f, 50.0f, 15.0f, width / 2.0f - 40.0f, 1.0f, 15.0f, -25.0f,
        width / 2.0f, 1.0f / 2.0f, 0.0f, 25.0f, 15.0f, 9.0f, false);
    g2.translate(0, -height + 60);
    drawCurve(g2, height - 35.0f, -5.0f, height - 50.0f, 10.0f, width / 2.0f - 40.0f, 1.0f,
        height - 35.0f, -25.0f, width / 2.0f, 1.0f / 2.0f, height - 20.0f, 25.0f, 25.0f, 7.0f, true);
    g2.setRenderingHints(oldHints);
  }
  protected void drawCurve(Graphics2D g2, float y1, float y1_offset, float y2, float y2_offset,
      float cx1, float cx1_offset, float cy1, float cy1_offset, float cx2, float cx2_offset,
      float cy2, float cy2_offset, float thickness, float speed, boolean invert) {
    float width = getWidth();
    float offset = (float) Math.sin(counter / (speed * Math.PI));
    float start_x = 0.0f;
    float start_y = offset * y1_offset + y1;
    float end_x = width;
    float end_y = offset * y2_offset + y2;
    float ctrl1_x = offset * cx1_offset + cx1;
    float ctrl1_y = offset * cy1_offset + cy1;
    float ctrl2_x = offset * cx2_offset + cx2;
    float ctrl2_y = offset * cy2_offset + cy2;
    GeneralPath thickCurve = new GeneralPath();
    thickCurve.moveTo(start_x, start_y);
    thickCurve.curveTo(ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, end_x, end_y);
    thickCurve.lineTo(end_x, end_y + thickness);
    thickCurve.curveTo(ctrl2_x, ctrl2_y + thickness, ctrl1_x, ctrl1_y + thickness, start_x, start_y
        + thickness);
    thickCurve.lineTo(start_x, start_y);
    Rectangle bounds = thickCurve.getBounds();
    if (!bounds.intersects(g2.getClipBounds())) {
      return;
    }
    GradientPaint painter = new GradientPaint(0, bounds.y, invert ? end : start, 0, bounds.y
        + bounds.height, invert ? start : end);
    Paint oldPainter = g2.getPaint();
    g2.setPaint(painter);
    g2.fill(thickCurve);
    g2.setPaint(oldPainter);
  }
}
/*
 * Copyright (c) 2007, Romain Guy All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. * Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. * Neither the name of the
 * TimingFramework project nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
class GradientPanel extends JPanel {
  protected BufferedImage gradientImage;
  protected Color gradientStart = new Color(204, 249, 124);
  protected Color gradientEnd = new Color(174, 222, 94);
  public GradientPanel() {
    this(new BorderLayout());
  }
  public GradientPanel(LayoutManager layout) {
    super(layout);
    addComponentListener(new GradientCacheManager());
  }
  @Override
  protected void paintComponent(Graphics g) {
    createImageCache();
    if (gradientImage != null) {
      g.drawImage(gradientImage, 0, 0, getWidth(), getHeight(), null);
    }
  }
  protected void createImageCache() {
    int width = 2;
    int height = getHeight();
    if (width == 0 || height == 0) {
      return;
    }
    if (gradientImage == null || width != gradientImage.getWidth()
        || height != gradientImage.getHeight()) {
      gradientImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      Graphics2D g2 = gradientImage.createGraphics();
      GradientPaint painter = new GradientPaint(0, 0, gradientEnd, 0, height / 2, gradientStart);
      g2.setPaint(painter);
      Rectangle2D rect = new Rectangle2D.Double(0, 0, width, height / 2.0);
      g2.fill(rect);
      painter = new GradientPaint(0, height / 2, gradientStart, 0, height, gradientEnd);
      g2.setPaint(painter);
      rect = new Rectangle2D.Double(0, (height / 2.0) - 1.0, width, height);
      g2.fill(rect);
      g2.dispose();
    }
  }
  private void disposeImageCache() {
    synchronized (gradientImage) {
      gradientImage.flush();
      gradientImage = null;
    }
  }
  private class GradientCacheManager implements ComponentListener {
    public void componentResized(ComponentEvent e) {
    }
    public void componentMoved(ComponentEvent e) {
    }
    public void componentShown(ComponentEvent e) {
    }
    public void componentHidden(ComponentEvent e) {
      disposeImageCache();
    }
  }
}
/*
 * Copyright (c) 2007, Romain Guy All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. * Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. * Neither the name of the
 * TimingFramework project nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * 
 * @author Romain Guy 
 */
class StackLayout implements LayoutManager2 {
  public static final String BOTTOM = "bottom";
  public static final String TOP = "top";
  private List components = new LinkedList();
  public void addLayoutComponent(Component comp, Object constraints) {
    synchronized (comp.getTreeLock()) {
      if (BOTTOM.equals(constraints)) {
        components.add(0, comp);
      } else if (TOP.equals(constraints)) {
        components.add(comp);
      } else {
        components.add(comp);
      }
    }
  }
  public void addLayoutComponent(String name, Component comp) {
    addLayoutComponent(comp, TOP);
  }
  public void removeLayoutComponent(Component comp) {
    synchronized (comp.getTreeLock()) {
      components.remove(comp);
    }
  }
  public float getLayoutAlignmentX(Container target) {
    return 0.5f;
  }
  public float getLayoutAlignmentY(Container target) {
    return 0.5f;
  }
  public void invalidateLayout(Container target) {
  }
  public Dimension preferredLayoutSize(Container parent) {
    synchronized (parent.getTreeLock()) {
      int width = 0;
      int height = 0;
      for (Component comp : components) {
        Dimension size = comp.getPreferredSize();
        width = Math.max(size.width, width);
        height = Math.max(size.height, height);
      }
      Insets insets = parent.getInsets();
      width += insets.left + insets.right;
      height += insets.top + insets.bottom;
      return new Dimension(width, height);
    }
  }
  public Dimension minimumLayoutSize(Container parent) {
    synchronized (parent.getTreeLock()) {
      int width = 0;
      int height = 0;
      for (Component comp : components) {
        Dimension size = comp.getMinimumSize();
        width = Math.max(size.width, width);
        height = Math.max(size.height, height);
      }
      Insets insets = parent.getInsets();
      width += insets.left + insets.right;
      height += insets.top + insets.bottom;
      return new Dimension(width, height);
    }
  }
  public Dimension maximumLayoutSize(Container target) {
    return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  }
  public void layoutContainer(Container parent) {
    synchronized (parent.getTreeLock()) {
      int width = parent.getWidth();
      int height = parent.getHeight();
      Rectangle bounds = new Rectangle(0, 0, width, height);
      int componentsCount = components.size();
      for (int i = 0; i < componentsCount; i++) {
        Component comp = components.get(i);
        comp.setBounds(bounds);
        parent.setComponentZOrder(comp, componentsCount - i - 1);
      }
    }
  }
}
/*
 * Copyright (c) 2007, Romain Guy All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. * Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. * Neither the name of the
 * TimingFramework project nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
class FileTreeWalker {
  private File path;
  private static final FileFilter directoryFilter = new FileFilter() {
    public boolean accept(File pathname) {
      return pathname.isDirectory();
    }
  };
  private FileFilter filter;
  public FileTreeWalker(File path) throws IOException {
    this(path, new FileFilter() {
      public boolean accept(File pathname) {
        return pathname.isFile();
      }
    });
  }
  public FileTreeWalker(File path, FileFilter filter) throws IOException {
    if (path == null || !path.exists() || path.isFile()) {
      throw new IOException("Path " + path + " is not a valid directory.");
    }
    this.path = path;
    this.filter = filter;
  }
  public void walk(FileTreeWalk walk) {
    walkDirectory(walk, path);
  }
  private void walkDirectory(FileTreeWalk walk, File dir) {
    File[] files = dir.listFiles(filter);
    for (File file : files) {
      walk.walk(file);
    }
    File[] dirs = dir.listFiles(directoryFilter);
    for (File subDir : dirs) {
      walkDirectory(walk, subDir);
    }
  }
}
/*
 * Copyright (c) 2007, Romain Guy All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. * Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. * Neither the name of the
 * TimingFramework project nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
interface FileTreeWalk {
  public void walk(File path);
}
/*
 * Copyright (c) 2007, Romain Guy All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. * Redistributions in
 * binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. * Neither the name of the
 * TimingFramework project nor the names of its contributors may be used to
 * endorse or promote products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * @author Romain Guy
 */
class UnixGlobFileFilter implements FileFilter {
  private Pattern pattern;
  public UnixGlobFileFilter(String filter) {
    pattern = Pattern.compile(globToRegex(filter));
  }
  public boolean accept(File file) {
    String path = file.getName();
    Matcher matcher = pattern.matcher(path);
    return matcher.matches();
  }
  private String globToRegex(String glob) {
    char c = '\0';
    boolean escape = false;
    boolean enclosed = false;
    StringBuffer buffer = new StringBuffer(glob.length());
    for (int i = 0; i < glob.length(); i++) {
      c = glob.charAt(i);
      if (escape) {
        buffer.append('\\');
        buffer.append(c);
        escape = false;
        continue;
      }
      switch (c) {
      case '*':
        buffer.append('.').append('*');
        break;
      case '?':
        buffer.append('.');
        break;
      case '\\':
        escape = true;
        break;
      case '.':
        buffer.append('\\').append('.');
        break;
      case '{':
        buffer.append('(');
        enclosed = true;
        break;
      case '}':
        buffer.append(')');
        enclosed = false;
        break;
      case ',':
        if (enclosed)
          buffer.append('|');
        else
          buffer.append(',');
        break;
      default:
        buffer.append(c);
      }
    }
    return buffer.toString();
  }
}