/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * 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.
 */
package org.jboss.util.graph;

import java.util.ArrayList;
import java.util.List;

/**
 * A named graph vertex with optional data.
 *
 * @author Scott.Stark@jboss.org
 * @author Ales.Justin@jboss.org
 * @version $Revision$
 * @param <T>
 */
@SuppressWarnings("unchecked")
public class Vertex<T>
{
   private List<Edge<T>> incomingEdges;
   private List<Edge<T>> outgoingEdges;
   private String name;
   private boolean mark;
   private int markState;
   private T data;

   /**
    * Calls this(null, null).
    */
   public Vertex()
   {
      this(null, null);
   }
   /**
    * Create a vertex with the given name and no data
    * @param n
    */
   public Vertex(String n)
   {
      this(n, null);
   }
   /**
    * Create a Vertex with name n and given data
    * @param n - name of vertex
    * @param data - data associated with vertex
    */
   public Vertex(String n, T data)
   {
      incomingEdges = new ArrayList<Edge<T>>();
      outgoingEdges = new ArrayList<Edge<T>>();
      name = n;
      mark = false;
      this.data = data;
   }

   /**
    * @return the possibly null name of the vertex
    */
   public String getName()
   {
      return name;
   }

   /**
    * @return the possibly null data of the vertex
    */
   public T getData()
   {
      return this.data;
   }

   /**
    * @param data The data to set.
    */
   public void setData(T data)
   {
      this.data = data;
   }

   /**
    * Add an edge to the vertex. If edge.from is this vertex, its an
    * outgoing edge. If edge.to is this vertex, its an incoming
    * edge. If neither from or to is this vertex, the edge is
    * not added.
    *
    * @param e - the edge to add
    * @return true if the edge was added, false otherwise
    */
   public boolean addEdge(Edge<T> e)
   {
      if (e.getFrom() == this)
         outgoingEdges.add(e);
      else if (e.getTo() == this)
         incomingEdges.add(e);
      else
         return false;
      return true;
   }

   /**
    * Add an outgoing edge ending at to.
    *
    * @param to - the destination vertex
    * @param cost the edge cost
    */
   public void addOutgoingEdge(Vertex<T> to, int cost)
   {
      Edge<T> out = new Edge<T>(this, to, cost);
      outgoingEdges.add(out);
   }

   /**
    * Add an incoming edge starting at from
    *
    * @param from - the starting vertex
    * @param cost the edge cost
    */
   public void addIncomingEdge(Vertex<T> from, int cost)
   {
      Edge<T> out = new Edge<T>(this, from, cost);
      incomingEdges.add(out);
   }

   /**
    * Check the vertex for either an incoming or outgoing edge
    * mathcing e.
    *
    * @param e the edge to check
    * @return true it has an edge
    */
   public boolean hasEdge(Edge<T> e)
   {
      if (e.getFrom() == this)
         return outgoingEdges.contains(e);
      else if (e.getTo() == this)
         return incomingEdges.contains(e);
      else
         return false;
   }

   /**
    * Remove an edge from this vertex
    *
    * @param e - the edge to remove
    * @return true if the edge was removed, false if the
    * edge was not connected to this vertex
    */
   public boolean remove(Edge<T> e)
   {
      if (e.getFrom() == this)
         outgoingEdges.remove(e);
      else if (e.getTo() == this)
         incomingEdges.remove(e);
      else
         return false;
      return true;
   }

   /**
    *
    * @return the count of incoming edges
    */
   public int getIncomingEdgeCount()
   {
      return incomingEdges.size();
   }

   /**
    * Get the ith incoming edge
    * @param i the index into incoming edges
    * @return ith incoming edge
    */
   public Edge<T> getIncomingEdge(int i)
   {
      return incomingEdges.get(i);
   }

   /**
    * Get the incoming edges
    * @return incoming edge list
    */
   public List getIncomingEdges()
   {
      return this.incomingEdges;
   }

   /**
    *
    * @return the count of incoming edges
    */
   public int getOutgoingEdgeCount()
   {
      return outgoingEdges.size();
   }

   /**
    * Get the ith outgoing edge
    * @param i the index into outgoing edges
    * @return ith outgoing edge
    */
   public Edge<T> getOutgoingEdge(int i)
   {
      return outgoingEdges.get(i);
   }

   /**
    * Get the outgoing edges
    * @return outgoing edge list
    */
   public List getOutgoingEdges()
   {
      return this.outgoingEdges;
   }

   /**
    * Search the outgoing edges looking for an edge whose's
    * edge.to == dest.
    * @param dest the destination
    * @return the outgoing edge going to dest if one exists,
    *    null otherwise.
    */
   public Edge<T> findEdge(Vertex<T> dest)
   {
      for (Edge<T> e : outgoingEdges)
      {
         if (e.getTo() == dest)
            return e;
      }
      return null;
   }

   /**
    * Search the outgoing edges for a match to e.
    *
    * @param e - the edge to check
    * @return e if its a member of the outgoing edges, null
    *    otherwise.
    */
   public Edge<T> findEdge(Edge<T> e)
   {
      if (outgoingEdges.contains(e))
         return e;
      else
         return null;
   }

   /**
    * What is the cost from this vertext to the dest vertex.
    *
    * @param dest - the destination vertex.
    * @return Return Integer.MAX_VALUE if we have no edge to dest,
    * 0 if dest is this vertex, the cost of the outgoing edge
    * otherwise.
    */
   public int cost(Vertex<T> dest)
   {
      if (dest == this)
         return 0;

      Edge<T> e = findEdge(dest);
      int cost = Integer.MAX_VALUE;
      if (e != null)
         cost = e.getCost();
      return cost;
   }

   /**
    * Is there an outgoing edge ending at dest.
    *
    * @param dest - the vertex to check
    * @return true if there is an outgoing edge ending
    *    at vertex, false otherwise.
    */
   public boolean hasEdge(Vertex<T> dest)
   {
      return (findEdge(dest) != null);
   }

   /**
    * Has this vertex been marked during a visit
    * @return true is visit has been called
    */
   public boolean visited()
   {
      return mark;
   }

   /**
    * Set the vertex mark flag.
    *
    */
   public void mark()
   {
      mark = true;
   }

   /**
    * Set the mark state to state.
    *
    * @param state the state
    */
   public void setMarkState(int state)
   {
      markState = state;
   }

   /**
    * Get the mark state value.
    * @return the mark state
    */
   public int getMarkState()
   {
      return markState;
   }

   /**
    * Visit the vertex and set the mark flag to true.
    *
    */
   public void visit()
   {
      mark();
   }

   /**
    * Clear the visited mark flag.
    *
    */
   public void clearMark()
   {
      mark = false;
   }

   /**
    * @return a string form of the vertex with in and out
    * edges.
    */
   public String toString()
   {
      StringBuffer tmp = new StringBuffer("Vertex(");
      tmp.append(name);
      tmp.append(", data=");
      tmp.append(data);
      tmp.append("), in:[");
      for (int i = 0; i < incomingEdges.size(); i++)
      {
         Edge<T> e = incomingEdges.get(i);
         if( i > 0 )
            tmp.append(',');
         tmp.append('{');
         tmp.append(e.getFrom().name);
         tmp.append(',');
         tmp.append(e.getCost());
         tmp.append('}');
      }
      tmp.append("], out:[");
      for (int i = 0; i < outgoingEdges.size(); i++)
      {
         Edge<T> e = outgoingEdges.get(i);
         if( i > 0 )
            tmp.append(',');
         tmp.append('{');
         tmp.append(e.getTo().name);
         tmp.append(',');
         tmp.append(e.getCost());
         tmp.append('}');
      }
      tmp.append(']');
      return tmp.toString();
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * 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.
 */
package org.jboss.util.file;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

/** A utility class for dealing with Jar files.

@author Scott.Stark@jboss.org
@version $Revision$
*/
public final class JarUtils
{
   /**
    * Hide the constructor
    */
   private JarUtils()
   {
   }

   /**
    *
This function will create a Jar archive containing the src
    * file/directory.  The archive will be written to the specified
    * OutputStream.

    *
    *
This is a shortcut for

    * jar(out, new File[] { src }, null, null, null);

    *
    * @param out The output stream to which the generated Jar archive is
    *        written.
    * @param src The file or directory to jar up.  Directories will be
    *        processed recursively.
    * @throws IOException
    */
   public static void jar(OutputStream out, File src) throws IOException
   {
      jar(out, new File[] { src }, null, null, null);
   }

   /**
    *
This function will create a Jar archive containing the src
    * file/directory.  The archive will be written to the specified
    * OutputStream.

    *
    *
This is a shortcut for

    * jar(out, src, null, null, null);

    *
    * @param out The output stream to which the generated Jar archive is
    *        written.
    * @param src The file or directory to jar up.  Directories will be
    *        processed recursively.
    * @throws IOException
    */
   public static void jar(OutputStream out, File[] src) throws IOException
   {
      jar(out, src, null, null, null);
   }

   /**
    *
This function will create a Jar archive containing the src
    * file/directory.  The archive will be written to the specified
    * OutputStream.  Directories are processed recursively, applying the
    * specified filter if it exists.
    *
    *
This is a shortcut for

    * jar(out, src, filter, null, null);

    *
    * @param out The output stream to which the generated Jar archive is
    *        written.
    * @param src The file or directory to jar up.  Directories will be
    *        processed recursively.
    * @param filter The filter to use while processing directories.  Only
    *        those files matching will be included in the jar archive.  If
    *        null, then all files are included.
    * @throws IOException
    */
   public static void jar(OutputStream out, File[] src, FileFilter filter)
      throws IOException
   {
      jar(out, src, filter, null, null);
   }

   /**
    *
This function will create a Jar archive containing the src
    * file/directory.  The archive will be written to the specified
    * OutputStream.  Directories are processed recursively, applying the
    * specified filter if it exists.
    *
    * @param out The output stream to which the generated Jar archive is
    *        written.
    * @param src The file or directory to jar up.  Directories will be
    *        processed recursively.
    * @param filter The filter to use while processing directories.  Only
    *        those files matching will be included in the jar archive.  If
    *        null, then all files are included.
    * @param prefix The name of an arbitrary directory that will precede all
    *        entries in the jar archive.  If null, then no prefix will be
    *        used.
    * @param man The manifest to use for the Jar archive.  If null, then no
    *        manifest will be included.
    * @throws IOException
    */
   public static void jar(OutputStream out, File[] src, FileFilter filter,
      String prefix, Manifest man) throws IOException
   {

      for (int i = 0; i < src.length; i++)
      {
         if (!src[i].exists())
         {
            throw new FileNotFoundException(src.toString());
         }
      }

      JarOutputStream jout;
      if (man == null)
      {
         jout = new JarOutputStream(out);
      }
      else
      {
         jout = new JarOutputStream(out, man);
      }
      if (prefix != null && prefix.length() > 0 && !prefix.equals("/"))
      {
         // strip leading '/'
         if (prefix.charAt(0) == '/')
         {
            prefix = prefix.substring(1);
         }
         // ensure trailing '/'
         if (prefix.charAt(prefix.length() - 1) != '/')
         {
            prefix = prefix + "/";
         }
      }
      else
      {
         prefix = "";
      }
      JarInfo info = new JarInfo(jout, filter);
      for (int i = 0; i < src.length; i++)
      {
         jar(src[i], prefix, info);
      }
      jout.close();
   }

   /**
    * This simple convenience class is used by the jar method to reduce the
    * number of arguments needed.  It holds all non-changing attributes
    * needed for the recursive jar method.
    */
   private static class JarInfo
   {
      public JarOutputStream out;
      public FileFilter filter;
      public byte[] buffer;

      public JarInfo(JarOutputStream out, FileFilter filter)
      {
         this.out = out;
         this.filter = filter;
         buffer = new byte[1024];
      }
   }

   /**
    * This recursive method writes all matching files and directories to
    * the jar output stream.
    */
   private static void jar(File src, String prefix, JarInfo info)
      throws IOException
   {

      JarOutputStream jout = info.out;
      if (src.isDirectory())
      {
         // create / init the zip entry
         prefix = prefix + src.getName() + "/";
         ZipEntry entry = new ZipEntry(prefix);
         entry.setTime(src.lastModified());
         entry.setMethod(JarOutputStream.STORED);
         entry.setSize(0L);
         entry.setCrc(0L);
         jout.putNextEntry(entry);
         jout.closeEntry();

         // process the sub-directories
         File[] files = src.listFiles(info.filter);
         for (int i = 0; i < files.length; i++)
         {
            jar(files[i], prefix, info);
         }
      }
      else if (src.isFile())
      {
         // get the required info objects
         byte[] buffer = info.buffer;

         // create / init the zip entry
         ZipEntry entry = new ZipEntry(prefix + src.getName());
         entry.setTime(src.lastModified());
         jout.putNextEntry(entry);

         // dump the file
         FileInputStream in = new FileInputStream(src);
         int len;
         while ((len = in.read(buffer, 0, buffer.length)) != -1)
         {
            jout.write(buffer, 0, len);
         }
         in.close();
         jout.closeEntry();
      }
   }

   public static void unjar(InputStream in, File dest) throws IOException
   {
      if (!dest.exists())
      {
         dest.mkdirs();
      }
      if (!dest.isDirectory())
      {
         throw new IOException("Destination must be a directory.");
      }
      JarInputStream jin = new JarInputStream(in);
      byte[] buffer = new byte[1024];

      String canonicalDocBasePrefix = dest.getCanonicalPath();
      if (!canonicalDocBasePrefix.endsWith(File.separator))
      {
         canonicalDocBasePrefix += File.separator;
      }

      ZipEntry entry = jin.getNextEntry();
      while (entry != null)
      {
         String fileName = entry.getName();
         if (fileName.charAt(fileName.length() - 1) == '/')
         {
            fileName = fileName.substring(0, fileName.length() - 1);
         }
         if (fileName.charAt(0) == '/')
         {
            fileName = fileName.substring(1);
         }
         if (File.separatorChar != '/')
         {
            fileName = fileName.replace('/', File.separatorChar);
         }
         File file = new File(dest, fileName);
         if (!file.getCanonicalPath().startsWith(canonicalDocBasePrefix))
         {
            throw new IOException("Illegal path: " + fileName);
         }

         if (entry.isDirectory())
         {
            // make sure the directory exists
            file.mkdirs();
            jin.closeEntry();
         }
         else
         {
            // make sure the directory exists
            File parent = file.getParentFile();
            if (parent != null && !parent.exists())
            {
               parent.mkdirs();
            }

            // dump the file
            OutputStream out = new FileOutputStream(file);
            int len = 0;
            while ((len = jin.read(buffer, 0, buffer.length)) != -1)
            {
               out.write(buffer, 0, len);
            }
            out.flush();
            out.close();
            jin.closeEntry();
            file.setLastModified(entry.getTime());
         }
         entry = jin.getNextEntry();
      }
      /* Explicity write out the META-INF/MANIFEST.MF so that any headers such
      as the Class-Path are see for the unpackaged jar
      */
      Manifest mf = jin.getManifest();
      if (mf != null)
      {
         File file = new File(dest, "META-INF/MANIFEST.MF");
         File parent = file.getParentFile();
         if( parent.exists() == false )
         {
            parent.mkdirs();
         }
         OutputStream out = new FileOutputStream(file);
         mf.write(out);
         out.flush();
         out.close();
      }
      jin.close();
   }

   /** Given a URL check if its a jar url(jar:<url>!/archive) and if it is,
    extract the archive entry into the given dest directory and return a file
    URL to its location. If jarURL is not a jar url then it is simply returned
    as the URL for the jar.

    @param jarURL the URL to validate and extract the referenced entry if its
      a jar protocol URL
    @param dest the directory into which the nested jar will be extracted.
    @return the file: URL for the jar referenced by the jarURL parameter.
    * @throws IOException
    */
   public static URL extractNestedJar(URL jarURL, File dest)
      throws IOException
   {
      // This may not be a jar URL so validate the protocol
      if( jarURL.getProtocol().equals("jar") == false )
         return jarURL;

      String destPath = dest.getAbsolutePath();
      URLConnection urlConn = jarURL.openConnection();
      JarURLConnection jarConn = (JarURLConnection) urlConn;
      // Extract the archive to dest/jarName-contents/archive
      String parentArchiveName = jarConn.getJarFile().getName();
      // Find the longest common prefix between destPath and parentArchiveName
      int length = Math.min(destPath.length(), parentArchiveName.length());
      int n = 0;
      while( n < length )
      {
         char a = destPath.charAt(n);
         char b = parentArchiveName.charAt(n);
         if( a != b )
            break;
         n ++;
      }
      // Remove any common prefix from parentArchiveName
      parentArchiveName = parentArchiveName.substring(n);

      File archiveDir = new File(dest, parentArchiveName+"-contents");
      if( archiveDir.exists() == false && archiveDir.mkdirs() == false )
         throw new IOException("Failed to create contents directory for archive, path="+archiveDir.getAbsolutePath());
      String archiveName = jarConn.getEntryName();
      File archiveFile = new File(archiveDir, archiveName);
      File archiveParentDir = archiveFile.getParentFile();
      if( archiveParentDir.exists() == false && archiveParentDir.mkdirs() == false )
         throw new IOException("Failed to create parent directory for archive, path="+archiveParentDir.getAbsolutePath());
      InputStream archiveIS = jarConn.getInputStream();
      FileOutputStream fos = new FileOutputStream(archiveFile);
      BufferedOutputStream bos = new BufferedOutputStream(fos);
      byte[] buffer = new byte[4096];
      int read;
      while( (read = archiveIS.read(buffer)) > 0 )
      {
         bos.write(buffer, 0, read);
      }
      archiveIS.close();
      bos.close();

      // Return the file url to the extracted jar
      return archiveFile.toURL();
   }

   public static void main(String[] args) throws Exception
   {
      if (args.length == 0)
      {
         System.out.println("usage: <x or c> <jar-archive> <files...>");
         System.exit(0);
      }
      if (args[0].equals("x"))
      {
         BufferedInputStream in = new BufferedInputStream(new FileInputStream(args[1]));
         File dest = new File(args[2]);
         unjar(in, dest);
      }
      else if (args[0].equals("c"))
      {
         BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(args[1]));
         File[] src = new File[args.length - 2];
         for (int i = 0; i < src.length; i++)
         {
            src[i] = new File(args[2 + i]);
         }
         jar(out, src);
      }
      else
      {
         System.out.println("Need x or c as first argument");
      }
   }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * 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.
 */
package org.jboss.util.collection;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A compound iterator, which iterates over all of the elements in the
 * given iterators.
 *
 * @version <tt>$Revision$</tt>
 * @author  Jason Dillon
 */
@SuppressWarnings("unchecked")
public class CompoundIterator
   implements Iterator
{
   /** The array of iterators to iterate over. */
   protected final Iterator iters[];

   /** The index of the current iterator. */
   protected int index;

   /**
    * Construct a CompoundIterator over the given array of iterators.
    *
    * @param iters   Array of iterators to iterate over.
    *
    * @throws IllegalArgumentException    Array is <kk>null</kk> or empty.
    */
   public CompoundIterator(final Iterator iters[]) {
      if (iters == null || iters.length == 0)
         throw new IllegalArgumentException("array is null or empty");

      this.iters = iters;
   }

   /**
    * Check if there are more elements.
    *
    * @return  True if there are more elements.
    */
   public boolean hasNext() {
      for (; index < iters.length; index++) {
         if (iters[index] != null && iters[index].hasNext()) {
            return true;
         }
      }

      return false;
   }

   /**
    * Return the next element from the current iterator.
    *
    * @return  The next element from the current iterator.
    *
    * @throws NoSuchElementException   There are no more elements.
    */
   public Object next() {
      if (!hasNext()) {
         throw new NoSuchElementException();
      }

      return iters[index].next();
   }

   /**
    * Remove the current element from the current iterator.
    *
    * @throws IllegalStateException
    * @throws UnsupportedOperationException
    */
   public void remove() {
      iters[index].remove();
   }
}


/*
 * Copyright (C) 2015 Sergej Shafarenka, halfbit.de
 *
 * 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.
 */
package de.halfbit.tinymachine;

import android.util.Log;
import android.util.SparseArray;

import java.lang.reflect.Method;
import java.util.HashMap;

import de.halfbit.tinymachine.StateHandler.Type;

/**
 * Finite-state machine implementation class.
 *

 *     Once created TinyMachine is capable to accept events
 *     and deliver them to a handler instance given in constructor. Handler
 *     instance defines public methods with {@link de.halfbit.tinymachine.StateHandler}
 *     annotation. Every method is intended to handler certain events in certain
 *     state. You can have as many handler methods as you need.
 *
 *

 *     When you receive an event, you just need to forward it to a TinyMachine
 *     instance and it will deliver it to the right handler method. Simply call
 *     {@link #fireEvent(Object)} for this. If there is no handler method for given event
 *     in given state, TinyMachine will silently ignore it.
 *
 *

 *     There is no static configuration for transitions between states. You need to program
 *     is inside your handler methods. Call {@link #transitionTo(int)} methods to move your
 *     state machine into a new state. TinyMachine will automatically fire
 *     Type.OnExit event for the current state and Type.OnEntry event
 *     for the new state. If there are handlers for this events, then they will be called.
 *
 * @author Sergej Shafarenka
 */
public class TinyMachine {

    private final Object mHandler;
    private final SparseArray<HashMap<Class<?>, Method>> mCallbacks;
    private final TaskQueue mTaskQueue;

    private String mTraceTag;
    private int mCurrentState;
    private boolean mQueueProcessed;

    /**
     * Creates new instance of FSM machine and assigns handler class
     * with public methods handling state transitions and events. Handler
     * methods must have {@link de.halfbit.tinymachine.StateHandler}
     * annotation describing which event in which state it will handle.
     *
     * @param handler      instance with handler methods
     * @param initialState initial state to put state machine into.
     *                     Type.OnEntry event is not reported
     *                     for the initial state.
     */
    public TinyMachine(Object handler, int initialState) {
        mHandler = handler;
        mCurrentState = initialState;
        mCallbacks = new SparseArray<>();
        mTaskQueue = new TaskQueue();

        final Method[] methods = handler.getClass().getMethods();
        Class<?>[] params;
        StateHandler ann;
        Class<?> eventType;
        for (Method method : methods) {
            if (method.isBridge() || method.isSynthetic()) {
                continue;
            }
            ann = method.getAnnotation(StateHandler.class);
            if (ann != null) {
                switch (ann.type()) {
                    case Type.OnEntry: {
                        eventType = OnEntry.class;
                        break;
                    }
                    case Type.OnExit: {
                        eventType = OnExit.class;
                        break;
                    }
                    case Type.OnEvent: {
                        params = method.getParameterTypes();
                        if (params.length < 1) {
                            throw new IllegalArgumentException(
                                    "Expect event parameter in @StateEventHandler method: "
                                            + method.getName());
                        }
                        eventType = params[0];
                        break;
                    }
                    default:
                        throw new IllegalArgumentException(
                                "Unsupported event type: " + ann.type());
                }

                final int state = ann.state();
                HashMap<Class<?>, Method> callbacks = mCallbacks.get(state);
                if (callbacks == null) {
                    callbacks = new HashMap<>();
                    mCallbacks.put(state, callbacks);
                }
                if (callbacks.put(eventType, method) != null) {
                    throw new IllegalArgumentException("Duplicate handler methods not allowed" +
                            ", method: " + method.getName());
                }
            }
        }
    }

    //-- public api

    /**
     * Forwards an event into state machine. State machine will deliver the event to a
     * handler methods responsible for its processing. If there is no handler method found,
     * then event gets silently ignored and this call has no effect.
     *
     * @param event event to be delivered to a handler method
     */
    public void fireEvent(Object event) {
        if (event == null) {
            throw new IllegalArgumentException("Event must not be null.");
        }
        mTaskQueue.offer(Task.obtainTask(Task.CODE_FIRE_EVENT, event, -1));
        if (!mQueueProcessed) processTaskQueue();
    }

    /**
     * Moves state machine in a new given state. If state machine is already in that state,
     * then this method has no effect. Otherwise, if exists, Type.OnExit event
     * handler for the current state is called first and then Type.OnEntry event
     * handler for new state is called.
     *
     * @param state new state to put state machine into
     */
    public void transitionTo(int state) {
        mTaskQueue.offer(Task.obtainTask(Task.CODE_TRANSITION, null, state));
        if (!mQueueProcessed) processTaskQueue();
    }

    /**
     * Enables traces and sets tag to be used for Log.d() output.
     * TinyMachine will trace all processed events and state transitions including
     * events for which handlers in current state are missed.
     *
     * @param tag   the name of tag to be used in Log.d()
     * @return      this instance
     */
    public TinyMachine setTraceTag(String tag) {
        mTraceTag = tag;
        if (mTraceTag != null) {
            log("current state", null);
        }
        return this;
    }

    /**
     * Returns current machine state.
     *
     * @return current machine state
     */
    public int getCurrentState() {
        return mCurrentState;
    }

    //-- implementation

    private void processTaskQueue() {
        mQueueProcessed = true;
        try {
            Task task;
            while((task = mTaskQueue.poll()) != null) {
                switch (task.code) {

                    case Task.CODE_FIRE_EVENT: {
                        Object event = task.event;
                        final Class<? extends Object> clazz = event.getClass();
                        fire(clazz, event, StateHandler.STATE_ANY);
                        fire(clazz, event, mCurrentState);
                        break;
                    }

                    case Task.CODE_TRANSITION: {
                        int state = task.state;
                        if (mCurrentState != state) {
                            fire(OnExit.class, null, StateHandler.STATE_ANY);
                            fire(OnExit.class, null, mCurrentState);
                            mCurrentState = state;
                            if (mTraceTag != null) {
                                log("new state", null);
                            }
                            fire(OnEntry.class, null, StateHandler.STATE_ANY);
                            fire(OnEntry.class, null, mCurrentState);
                        }
                        break;
                    }

                    default: throw new IllegalStateException("wrong code: " + task.code);
                }
                task.recycle();
            }
        } finally {
            mQueueProcessed = false;
        }
    }

    private void fire(Class<?> handlerType, Object event, int state) {
        Method method = null;
        HashMap<Class<?>, Method> callbacks = mCallbacks.get(state);
        if (callbacks != null) {
            method = callbacks.get(handlerType);
        }

        if (method == null) {
            if (mTraceTag != null && state != StateHandler.STATE_ANY) {
                // log missing handler method
                if (handlerType == OnEntry.class) {
                    log("OnEntry", "no handler method");
                } else if (handlerType == OnExit.class) {
                    log("OnExit", "no handler method");
                } else {
                    log("OnEvent", "no handler method, event=" + event);
                }
            }
            return; // no handler, exit
        }

        try {
            int paramsCount = method.getParameterTypes().length;
            if (event == null) {
                if (mTraceTag != null) {
                    if (handlerType == OnEntry.class) {
                        log("OnEntry", null);
                    } else {
                        log("OnExit", null);
                    }
                }
                switch (paramsCount) {
                    case 0:
                        method.invoke(mHandler);
                        break;
                    case 1:
                        method.invoke(mHandler, this);
                        break;
                    default:
                        throw new IllegalArgumentException(
                                "@StateEventHandler method must have 0 or 1 parameters");
                }
            } else {
                if (mTraceTag != null) {
                    log("OnEvent", "OnEvent, event=" + event);
                }
                switch (paramsCount) {
                    case 1:
                        method.invoke(mHandler, event);
                        break;
                    case 2:
                        method.invoke(mHandler, event, this);
                        break;
                    default:
                        throw new IllegalArgumentException(
                                "@StateEventHandler method must have 1 or 2 parameters");
                }
            }
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new IllegalStateException("Exception in @StateEventHandler method. "
                        + "See stack trace for more details", e);
            }
        }
    }

    private void log(String eventType, String message) {
        Log.d(mTraceTag, "  [" + mCurrentState + "] "
                + eventType + (message == null ? "" : ", " + message));
    }

    //region Inner classes (same as in tinybus)

    private static class OnEntry {}
    private static class OnExit {}

    private static class Task {

        private static final TaskPool POOL = new TaskPool(6);

        public static final int CODE_FIRE_EVENT = 0;
        public static final int CODE_TRANSITION = 1;

        // task as linked list item
        public Task prev;

        // general purpose
        public int code;
        public Object event;
        public int state;

        private Task() { }

        public static Task obtainTask(int code, Object event, int state) {
            Task task;
            synchronized (POOL) {
                task = POOL.acquire();
            }
            task.code = code;
            task.event = event;
            task.state = state;
            task.prev = null;
            return task;
        }

        public void recycle() {
            event = null;
            synchronized (POOL) {
                POOL.release(this);
            }
        }

    }

    /** Task pool for better reuse of task instances */
    private static class TaskPool {

        private final int mMaxSize;
        private int mSize;
        private Task tail;

        public TaskPool(int maxSize) {
            mMaxSize = maxSize;
        }

        Task acquire() {
            Task acquired = tail;
            if (acquired == null) {
                acquired = new Task();
            } else {
                tail = acquired.prev;
                mSize--;
            }
            return acquired;
        }

        void release(Task task) {
            if (mSize < mMaxSize) {
                task.prev = tail;
                tail = task;
                mSize++;
            }
        }
    }

    private static class TaskQueue {

        protected Task head;
        protected Task tail;

        public void offer(Task task) {
            if (tail == null) {
                tail = head = task;
            } else {
                tail.prev = task;
                tail = task;
            }
        }

        public Task poll() {
            if (head == null) {
                return null;
            } else {
                Task task = head;
                head = head.prev;
                if (head == null) {
                    tail = null;
                }
                return task;
            }
        }
    }

    //endregion

}


package org.structs4java;

import static com.google.common.collect.Iterables.addAll;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static java.util.Arrays.asList;
import static org.eclipse.xtext.util.Strings.concat;
import static org.eclipse.xtext.util.Strings.emptyIfNull;
import static org.eclipse.xtext.util.Strings.isEmpty;
import static org.eclipse.xtext.util.Strings.split;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
import org.eclipse.xtend.core.compiler.batch.BootClassLoader;
import org.eclipse.xtend.core.macro.ProcessorInstanceForJvmTypeProvider.ProcessorClassloaderAdapter;
import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider;
import org.eclipse.xtext.common.types.access.impl.IndexedJvmTypeAccess;
import org.eclipse.xtext.common.types.descriptions.IStubGenerator;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.generator.GeneratorDelegate;
import org.eclipse.xtext.generator.JavaIoFileSystemAccess;
import org.eclipse.xtext.mwe.NameBasedFilter;
import org.eclipse.xtext.mwe.PathTraverser;
import org.eclipse.xtext.parser.IEncodingProvider;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.resource.CompilerPhases;
import org.eclipse.xtext.resource.FileExtensionProvider;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.resource.impl.ResourceSetBasedResourceDescriptions;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.validation.CheckMode;
import org.eclipse.xtext.validation.IResourceValidator;
import org.eclipse.xtext.validation.Issue;
import org.structs4java.structs4JavaDsl.StructsFile;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.google.inject.Provider;

public class StructsBatchCompiler {

	private static final Logger log = Logger.getLogger(StructsBatchCompiler.class);

	private final static class SeverityFilter implements Predicate<Issue> {
		private static final SeverityFilter WARNING = new SeverityFilter(Severity.WARNING);
		private static final SeverityFilter ERROR = new SeverityFilter(Severity.ERROR);
		private Severity severity;

		private SeverityFilter(Severity severity) {
			this.severity = severity;
		}

		public boolean apply(Issue issue) {
			return this.severity == issue.getSeverity();
		}
	}

	private static final FileFilter ACCEPT_ALL_FILTER = new FileFilter() {
		public boolean accept(File pathname) {
			return true;
		}
	};

	private XtextResourceSet resourceSet;
	@Inject
	private Provider<JavaIoFileSystemAccess> javaIoFileSystemAccessProvider;
	@Inject
	private FileExtensionProvider fileExtensionProvider;
	@Inject
	private Provider<ResourceSetBasedResourceDescriptions> resourceSetDescriptionsProvider;
	@Inject
	private GeneratorDelegate generator;
	@Inject
	private IndexedJvmTypeAccess indexedJvmTypeAccess;
	@Inject
	private IEncodingProvider.Runtime encodingProvider;
	@Inject
	private IResourceDescription.Manager resourceDescriptionManager;
	@Inject
	private CompilerPhases compilerPhases;
	@Inject
	private IStubGenerator stubGenerator;

	private Writer debugWriter;
	private String sourcePath;
	private String classPath;
	private String structSourceRoot;
	/**
	 * @since 2.7
	 */
	private String bootClassPath;
	private boolean useCurrentClassLoaderAsParent;
	private String outputPath;
	private String fileEncoding;
	private String complianceLevel = "1.5";
	private boolean verbose = false;
	private String tempDirectory = System.getProperty("java.io.tmpdir");
	private boolean deleteTempDirectory = true;
	private List<File> tempFolders = Lists.newArrayList();
	private boolean writeTraceFiles = true;
	private ClassLoader currentClassLoader = getClass().getClassLoader();

	public void setCurrentClassLoader(ClassLoader currentClassLoader) {
		this.currentClassLoader = currentClassLoader;
	}

	public void setUseCurrentClassLoaderAsParent(boolean useCurrentClassLoaderAsParent) {
		this.useCurrentClassLoaderAsParent = useCurrentClassLoaderAsParent;
	}

	public String getTempDirectory() {
		return tempDirectory;
	}

	public void setTempDirectory(String tempDirectory) {
		this.tempDirectory = tempDirectory;
	}

	public boolean isWriteTraceFiles() {
		return writeTraceFiles;
	}

	public void setWriteTraceFiles(boolean writeTraceFiles) {
		this.writeTraceFiles = writeTraceFiles;
	}

	public void setResourceSet(XtextResourceSet resourceSet) {
		this.resourceSet = resourceSet;
	}

	public boolean isDeleteTempDirectory() {
		return deleteTempDirectory;
	}

	public void setDeleteTempDirectory(boolean deletetempDirectory) {
		this.deleteTempDirectory = deletetempDirectory;
	}

	public Writer getDebugWriter() {
		if (debugWriter == null) {
			debugWriter = new Writer() {
				@Override
				public void write(char[] data, int offset, int count) throws IOException {
					String message = String.copyValueOf(data, offset, count);
					if (!Strings.isEmpty(message.trim())) {
						log.debug(message);
					}
				}

				@Override
				public void flush() throws IOException {
				}

				@Override
				public void close() throws IOException {
				}
			};
		}
		return debugWriter;
	}

	public void setClassPath(String classPath) {
		this.classPath = classPath;
	}

	/**
	 * @since 2.7
	 */
	public void setBootClassPath(String bootClassPath) {
		this.bootClassPath = bootClassPath;
	}

	public void setOutputPath(String outputPath) {
		this.outputPath = outputPath;
	}

	public void setSourcePath(String sourcePath) {
		this.sourcePath = sourcePath;
	}

	public void setStructSourceRoot(String structSourceRoot) {
		this.structSourceRoot = structSourceRoot;
	}

	protected String getComplianceLevel() {
		return complianceLevel;
	}

	public void setVerbose(boolean verbose) {
		this.verbose = verbose;
	}

	protected boolean isVerbose() {
		return verbose;
	}

	public String getFileEncoding() {
		return fileEncoding;
	}

	public void setFileEncoding(String encoding) {
		this.fileEncoding = encoding;
	}

	public boolean compile() {
		try {
			resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);

			File classDirectory = createTempDir("classes");
			try {
				compilerPhases.setIndexing(resourceSet, true);
				// install a type provider without index lookup for the first
				// phase
				installJvmTypeProvider(resourceSet, classDirectory, true);
				loadStructsFiles(resourceSet);
				File sourceDirectory = createStubs(resourceSet);
				if (!preCompileStubs(sourceDirectory, classDirectory)) {
					log.warn(
							"Compilation of stubs and existing Java code had errors. This is expected and usually is not a problem. To view the compilation errors re-run the build with debug enabled (-X).");
				}
			} finally {
				compilerPhases.setIndexing(resourceSet, false);
			}
			// install a fresh type provider for the second phase, so we clear
			// all previously cached classes and misses.
			installJvmTypeProvider(resourceSet, classDirectory, false);
			EcoreUtil.resolveAll(resourceSet);
			List<Issue> issues = validate(resourceSet);
			Iterable<Issue> errors = Iterables.filter(issues, SeverityFilter.ERROR);
			Iterable<Issue> warnings = Iterables.filter(issues, SeverityFilter.WARNING);
			reportIssues(Iterables.concat(errors, warnings));
			if (!Iterables.isEmpty(errors)) {
				return false;
			}
			generateJavaFiles(resourceSet);
		} finally {
			if (isDeleteTempDirectory()) {
				deleteTmpFolders();
			}
		}
		return true;
	}

	protected ResourceSet loadStructsFiles(final ResourceSet resourceSet) {
		encodingProvider.setDefaultEncoding(getFileEncoding());
		final NameBasedFilter nameBasedFilter = new NameBasedFilter();
		nameBasedFilter.setExtension(fileExtensionProvider.getPrimaryFileExtension());
		PathTraverser pathTraverser = new PathTraverser();
		List<String> sourcePathDirectories = getStructsSourcePathDirectories();
		Multimap<String, URI> pathes = pathTraverser.resolvePathes(sourcePathDirectories, new Predicate<URI>() {
			public boolean apply(URI input) {
				boolean matches = nameBasedFilter.matches(input);
				return matches;
			}
		});
		for (String src : pathes.keySet()) {
			URI baseDir = URI.createFileURI(src + "/");
			String identifier = Joiner.on("_").join(baseDir.segments());
			URI platformResourceURI = URI.createPlatformResourceURI(identifier + "/", true);
			resourceSet.getURIConverter().getURIMap().put(platformResourceURI, baseDir);
			for (URI uri : pathes.get(src)) {
				URI uriToUse = uri.replacePrefix(baseDir, platformResourceURI);
				log.info("load structs file '" + uriToUse + "'");
				resourceSet.getResource(uriToUse, true);
			}
		}
		return resourceSet;
	}

	protected File createStubs(ResourceSet resourceSet) {
		File outputDirectory = createTempDir("stubs");
		JavaIoFileSystemAccess fileSystemAccess = javaIoFileSystemAccessProvider.get();
		fileSystemAccess.setOutputPath(outputDirectory.toString());
		List<Resource> resources = Lists.newArrayList(resourceSet.getResources());
		for (Resource resource : resources) {
			IResourceDescription description = resourceDescriptionManager.getResourceDescription(resource);
			stubGenerator.doGenerateStubs(fileSystemAccess, description);
		}
		return outputDirectory;
	}

	protected boolean preCompileStubs(File tmpSourceDirectory, File classDirectory) {
		List<String> commandLine = Lists.newArrayList();
		// todo args
		if (isVerbose()) {
			commandLine.add("-verbose");
		}
		if (!isEmpty(bootClassPath)) {
			commandLine.add("-bootclasspath \"" + concat(File.pathSeparator, getBootClassPathEntries()) + "\"");
		}
		if (!isEmpty(classPath)) {
			commandLine.add("-cp \"" + concat(File.pathSeparator, getClassPathEntries()) + "\"");
		}
		commandLine.add("-d \"" + classDirectory.toString() + "\"");
		commandLine.add("-" + getComplianceLevel());
		commandLine.add("-proceedOnError");
		List<String> sourceDirectories = newArrayList(getSourcePathDirectories());
		sourceDirectories.add(tmpSourceDirectory.toString());
		commandLine.add(concat(" ", transform(sourceDirectories, new Function<String, String>() {

			public String apply(String path) {
				return "\"" + path + "\"";
			}
		})));
		log.debug("invoke batch compiler with '" + concat(" ", commandLine) + "'");
		return BatchCompiler.compile(concat(" ", commandLine), new PrintWriter(getDebugWriter()),
				new PrintWriter(getDebugWriter()), null);
	}

	protected List<Issue> validate(ResourceSet resourceSet) {
		List<Issue> issues = Lists.newArrayList();
		List<Resource> resources = Lists.newArrayList(resourceSet.getResources());
		for (Resource resource : resources) {
			IResourceServiceProvider resourceServiceProvider = IResourceServiceProvider.Registry.INSTANCE
					.getResourceServiceProvider(resource.getURI());
			if (resourceServiceProvider != null) {
				IResourceValidator resourceValidator = resourceServiceProvider.getResourceValidator();
				List<Issue> result = resourceValidator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl);
				addAll(issues, result);
			}
		}
		return issues;
	}

	/**
	 * Installs the complete JvmTypeProvider including index access into the
	 * {@link ResourceSet}. The lookup classpath is enhanced with the given tmp
	 * directory.
	 *
	 */
	@Deprecated
	protected void installJvmTypeProvider(XtextResourceSet resourceSet, File tmpClassDirectory) {
		internalInstallJvmTypeProvider(resourceSet, tmpClassDirectory, false);
	}

	/**
	 * Installs the JvmTypeProvider optionally including index access into the
	 * {@link ResourceSet}. The lookup classpath is enhanced with the given tmp
	 * directory.
	 */
	protected void installJvmTypeProvider(XtextResourceSet resourceSet, File tmpClassDirectory,
			boolean skipIndexLookup) {
		if (skipIndexLookup) {
			internalInstallJvmTypeProvider(resourceSet, tmpClassDirectory, skipIndexLookup);
		} else {
			// delegate to the deprecated signature in case it was overridden by
			// clients
			installJvmTypeProvider(resourceSet, tmpClassDirectory);
		}
	}

	/**
	 * Performs the actual installation of the JvmTypeProvider.
	 */
	private void internalInstallJvmTypeProvider(XtextResourceSet resourceSet, File tmpClassDirectory,
			boolean skipIndexLookup) {
		Iterable<String> classPathEntries = concat(getClassPathEntries(), getSourcePathDirectories(),
				asList(tmpClassDirectory.toString()));
		classPathEntries = filter(classPathEntries, new Predicate<String>() {
			public boolean apply(String input) {
				return !Strings.isEmpty(input.trim());
			}
		});
		Function<String, URL> toUrl = new Function<String, URL>() {
			public URL apply(String from) {
				try {
					return new File(from).toURI().toURL();
				} catch (MalformedURLException e) {
					throw new RuntimeException(e);
				}
			}
		};
		Iterable<URL> classPathUrls = Iterables.transform(classPathEntries, toUrl);
		log.debug("classpath used for Struct compilation : " + classPathUrls);
		ClassLoader parentClassLoader;
		if (useCurrentClassLoaderAsParent) {
			parentClassLoader = currentClassLoader;
		} else {
			if (isEmpty(bootClassPath)) {
				parentClassLoader = ClassLoader.getSystemClassLoader().getParent();
			} else {
				Iterable<URL> bootClassPathUrls = Iterables.transform(getBootClassPathEntries(), toUrl);
				parentClassLoader = new BootClassLoader(toArray(bootClassPathUrls, URL.class));
			}
		}
		ClassLoader urlClassLoader = new URLClassLoader(toArray(classPathUrls, URL.class), parentClassLoader);
		new ClasspathTypeProvider(urlClassLoader, resourceSet, skipIndexLookup ? null : indexedJvmTypeAccess);
		resourceSet.setClasspathURIContext(urlClassLoader);

		// for annotation processing we need to have the compiler's classpath as
		// a parent.
		URLClassLoader urlClassLoaderForAnnotationProcessing = new URLClassLoader(toArray(classPathUrls, URL.class),
				currentClassLoader);
		resourceSet.eAdapters().add(new ProcessorClassloaderAdapter(urlClassLoaderForAnnotationProcessing));
	}

	protected void reportIssues(Iterable<Issue> issues) {
		for (Issue issue : issues) {
			StringBuilder issueBuilder = createIssueMessage(issue);
			if (Severity.ERROR == issue.getSeverity()) {
				log.error(issueBuilder.toString());
			} else if (Severity.WARNING == issue.getSeverity()) {
				log.warn(issueBuilder.toString());
			}
		}
	}

	private StringBuilder createIssueMessage(Issue issue) {
		StringBuilder issueBuilder = new StringBuilder("\n");
		issueBuilder.append(issue.getSeverity()).append(": \t");
		URI uriToProblem = issue.getUriToProblem();
		if (uriToProblem != null) {
			URI resourceUri = uriToProblem.trimFragment();
			issueBuilder.append(resourceUri.lastSegment()).append(" - ");
			if (resourceUri.isFile()) {
				issueBuilder.append(resourceUri.toFileString());
			}
		}
		issueBuilder.append("\n").append(issue.getLineNumber()).append(": ").append(issue.getMessage());
		return issueBuilder;
	}

	protected void generateJavaFiles(ResourceSet resourceSet) {
		JavaIoFileSystemAccess javaIoFileSystemAccess = javaIoFileSystemAccessProvider.get();
		javaIoFileSystemAccess.setOutputPath(outputPath);
		javaIoFileSystemAccess.setWriteTrace(writeTraceFiles);

		for (Resource resource : newArrayList(resourceSet.getResources())) {
			if (getRootEObject(resource) instanceof StructsFile) {
				log.debug("Generating source for: " + resource);
				generator.doGenerate(resource, javaIoFileSystemAccess);
			}
		}
	}

	private EObject getRootEObject(Resource resource) {
		if (resource == null) {
			return null;
		}
		if (resource.getContents() == null || resource.getContents().size() == 0) {
			return null;
		}
		return resource.getContents().get(0);
	}

	protected ResourceSetBasedResourceDescriptions getResourceDescriptions(ResourceSet resourceSet) {
		ResourceSetBasedResourceDescriptions resourceDescriptions = resourceSetDescriptionsProvider.get();
		resourceDescriptions.setContext(resourceSet);
		resourceDescriptions.setRegistry(IResourceServiceProvider.Registry.INSTANCE);
		return resourceDescriptions;
	}

	protected StructsFile getStructsFile(Resource resource) {
		XtextResource xtextResource = (XtextResource) resource;
		IParseResult parseResult = xtextResource.getParseResult();
		if (parseResult != null) {
			EObject model = parseResult.getRootASTElement();
			if (model instanceof StructsFile) {
				StructsFile structsFile = (StructsFile) model;
				return structsFile;
			}
		}
		return null;
	}

	protected List<String> getClassPathEntries() {
		return getDirectories(classPath);
	}

	/**
	 * @since 2.7
	 */
	protected List<String> getBootClassPathEntries() {
		return getDirectories(bootClassPath);
	}

	protected List<String> getSourcePathDirectories() {
		return getDirectories(sourcePath);
	}

	protected List<String> getStructsSourcePathDirectories() {
		return getDirectories(structSourceRoot);
	}

	protected List<String> getDirectories(String path) {
		if (Strings.isEmpty(path)) {
			return Lists.newArrayList();
		}
		final List<String> split = split(emptyIfNull(path), File.pathSeparator);
		return transform(split, new Function<String, String>() {
			public String apply(String from) {
				return new File(new File(from).getAbsoluteFile().toURI().normalize()).getAbsolutePath();
			}
		});
	}

	protected File createTempDir(String prefix) {
		File tempDir = new File(getTempDirectory(), prefix + System.nanoTime());
		cleanFolder(tempDir, ACCEPT_ALL_FILTER, true, true);
		if (!tempDir.mkdirs()) {
			throw new RuntimeException("Error creating temp directory '" + tempDir.getAbsolutePath() + "'");
		}
		tempFolders.add(tempDir);
		return tempDir;
	}

	protected void deleteTmpFolders() {
		for (File file : tempFolders) {
			cleanFolder(file, ACCEPT_ALL_FILTER, true, true);
		}
	}

	// FIXME: use Files#cleanFolder after the maven distro availability of
	// version 2.2.x
	protected static boolean cleanFolder(File parentFolder, FileFilter filter, boolean continueOnError,
			boolean deleteParentFolder) {
		if (!parentFolder.exists()) {
			return true;
		}
		if (filter == null)
			filter = ACCEPT_ALL_FILTER;
		log.debug("Cleaning folder " + parentFolder.toString());
		final File[] contents = parentFolder.listFiles(filter);
		for (int j = 0; j < contents.length; j++) {
			final File file = contents[j];
			if (file.isDirectory()) {
				if (!cleanFolder(file, filter, continueOnError, true) && !continueOnError)
					return false;
			} else {
				if (!file.delete()) {
					log.warn("Couldn't delete " + file.getAbsolutePath());
					if (!continueOnError)
						return false;
				}
			}
		}
		if (deleteParentFolder) {
			if (parentFolder.list().length == 0 && !parentFolder.delete()) {
				log.warn("Couldn't delete " + parentFolder.getAbsolutePath());
				return false;
			}
		}
		return true;
	}

}


package eu.lestard.fluxfx;

import javafx.application.Platform;

import org.reactfx.EventSource;
import org.reactfx.EventStream;

/**
 * The central dispatcher that manages incoming {@link Action}s and provides them as EventStream so that
 * {@link Store}s can subscribe them.
 *
 * Views don't need to care about the dispatcher because there is a {@link View#publishAction(Action)} method in the {@link View}
 * interface that should be used to publish actions.
 *
 * Only external systems (f.e. backend services) that like to publish actions have to use this dispatcher class.
 *
 */
public class Dispatcher {

    private static final Dispatcher SINGLETON = new Dispatcher();

    private final EventSource<Action> actionStream = new EventSource<>();

    private Dispatcher() {
    }

    /**
     * @return a singleton instance of the Dispatcher.
     */
    public static Dispatcher getInstance() {
        return SINGLETON;
    }

    /**
     * Dispatch the given action. Dispatching is always done on the JavaFX application
     * thread, even if this method is called from another thread.
     *
     * @param action the action that will be dispatched to the stores.
     */
    public void dispatchOnFxThread(Action action) {
        if(Platform.isFxApplicationThread()) {
            actionStream.push(action);
        } else {
            Platform.runLater(() -> actionStream.push(action));
        }
    }

    /**
     * Dispatch the given action.
     *
     * @param action the action that will be dispatched to the stores.
     */
    public void dispatch(Action action) {
        actionStream.push(action);
    }

    /**
     * @return an event-stream of all published actions.
     */
    public EventStream<Action> getActionStream(){
        return actionStream;
    }

    /**
     * A filtered event-stream of actions of the given type.
     *
     * @param actionType the class type of the action.
     * @param <T> the generic type of the action.
     * @return an event-stream of all actions of the given type.
     */
    @SuppressWarnings("unchecked")
    <T extends Action> EventStream<T> getActionStream(Class<T> actionType) {
        return Dispatcher.getInstance()
                .getActionStream()
                .filter(action -> action.getClass().equals(actionType))
                .map(action -> (T) action);
    }

}


package eu.lestard.fluxfx;

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.util.Callback;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;

/**
 * A utility to load FXML files via a naming convention.
 *
 * In our terminology a "view" consists of a FXML file and a view class that implements the {@link View}
 * interface. This class has to be defined as "fx:controller" in the FXML file.
 *
 * The naming convention assumes that the view class and the fxml file have the same name (excluding file extension)
 * and are contained in the same directory/package. For example:
 *
 * ```java
 * package com.example.myapp;
 *
 * public class MyView implements View {
 *     ...
 * }
 * ```
 *
 * ```xml
 * <?xml version="1.0" encoding="UTF-8"?>
 *     ...
 * <VBox fx:controller="com.example.myapp.MyView" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
 * </VBox>
 * ```
 *
 * The fxml file should be named "MyView.fxml" in a directory "com/example/myapp".
 *
 * It can then be loaded like this:
 *
 * ```java
 * Parent parent = ViewLoader.load(MyView.class);
 * ```
 *
 * If you need a reference to the created view class see this code:
 * ```java
 * Tuple<MyView> tuple = ViewLoader.loadTuple(MyView.class);
 *
 * Parent parent = tuple.getParent();
 * MyView myView = tuple.getController();
 * ````
 *
 * Optionally you can provide a global ResourceBundle with the {@link #setResourceBundle(ResourceBundle)}.
 *
 *
 * By default new instances of controller/view classes are created by the {@link FXMLLoader}.
 * This can be overwritten by defining a custom Dependency-Injection function {@link #setDependencyInjector(Callback)}.
 * This way you can use the dependency injection library of your choice.
 *
 */
public class ViewLoader {

    public static class Tuple <T extends View> {
        private Parent parent;
        private T controller;

        Tuple(Parent parent, T controller) {
            this.parent = parent;
            this.controller = controller;
        }

        public Parent getParent() {
            return parent;
        }

        public T getController() {
            return controller;
        }
    }

    private static Callback<Class<?>, Object> injector;

    private static ResourceBundle resourceBundle;

    /**
     * Define a function that acts as dependency injection mechanism.
     * This is similar to the "ControllerFactory" that can be defined for JavaFX {@link FXMLLoader} with {@link FXMLLoader#setControllerFactory(Callback)}.
     *
     * @param injector the injector function.
     */
    public static void setDependencyInjector(Callback<Class<?>, Object> injector){
        ViewLoader.injector = injector;
    }

    public static void setResourceBundle(ResourceBundle resourceBundle) {
        ViewLoader.resourceBundle = resourceBundle;
    }

    public static <T extends Parent> T load(Class<? extends View> viewClass) {
        final FXMLLoader fxmlLoader = createFxmlLoader(viewClass);

        try {
            return fxmlLoader.load();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T extends View> Tuple<T> loadTuple(Class<T> viewClass) {
        final FXMLLoader fxmlLoader = createFxmlLoader(viewClass);

        try {
            return new Tuple<>(fxmlLoader.load(), fxmlLoader.getController());
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private static FXMLLoader createFxmlLoader(Class<? extends View> viewClass) {
        final String fxmlPathAsString = createFxmlPath(viewClass);
        final URL fxmlPath = ViewLoader.class.getResource(fxmlPathAsString);

        if(fxmlPath == null) {
            throw new IllegalArgumentException("Can't load View " + viewClass + ". FXML File not found under:" + fxmlPathAsString);
        }

        FXMLLoader fxmlLoader = new FXMLLoader(fxmlPath);
        fxmlLoader.setLocation(fxmlPath);
        fxmlLoader.setResources(resourceBundle);

        if(injector != null) {
            fxmlLoader.setControllerFactory(injector);
        }
        return fxmlLoader;
    }

    /**
     * Taken from mvvmFX
     */
    private static String createFxmlPath(Class<?> viewType) {
        final StringBuilder pathBuilder = new StringBuilder();

        pathBuilder.append("/");

        if (viewType.getPackage() != null) {
            pathBuilder.append(viewType.getPackage().getName().replaceAll("\\.", "/"));
            pathBuilder.append("/");
        }

        pathBuilder.append(viewType.getSimpleName());
        pathBuilder.append(".fxml");

        return pathBuilder.toString();
    }
}


package eu.lestard.fluxfx.utils;

import eu.lestard.fluxfx.View;
import eu.lestard.fluxfx.ViewLoader;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

public class ViewCellFactory <T, V extends View> implements Callback<ListView<T>, ListCell<T>> {

    private Map<T, ViewLoader.Tuple<V>> cache = new HashMap<>();
    private final Class<V> viewType;

    private BiConsumer<T, V> updateFunction;

    public ViewCellFactory(Class<V> viewType, BiConsumer<T, V> updateFunction) {
        this.viewType = viewType;
        this.updateFunction = updateFunction;
    }

    @Override
    public ListCell<T> call(ListView<T> param) {
        return new ListCell<T>() {
            @Override
            protected void updateItem(T item, boolean empty) {
                super.updateItem(item, empty);

                if(empty) {
                    setText(null);
                    setGraphic(null);
                }else {
                    setText(null);

                    if(item != null) {
                        if(!cache.containsKey(item)){
                            final ViewLoader.Tuple<V> tuple = ViewLoader.loadTuple(viewType);
                            cache.put(item, tuple);
                        }

                        final ViewLoader.Tuple<V> tuple = cache.get(item);

                        updateFunction.accept(item, tuple.getController());
                        setGraphic(tuple.getParent());
                    }
                }
            }
        };
    }

    /**
     * Update all views by invoking the update function ({@link #getUpdateFunction()}).
     */
    public void updateViews() {
        cache.entrySet()
                .forEach(entry -> updateFunction.accept(entry.getKey(), entry.getValue().getController()));
    }

    /**
     * Update all views that match the given predicate by invoking the update function ({@link #getUpdateFunction()}).
     */
    public void updateViews(Predicate<T> predicate) {
        cache.entrySet()
                .stream()
                .filter(entry -> predicate.test(entry.getKey()))
                .forEach(entry -> updateFunction.accept(entry.getKey(), entry.getValue().getController()));
    }

    public BiConsumer<T, V> getUpdateFunction() {
        return updateFunction;
    }

    public void setUpdateFunction(BiConsumer<T, V> updateFunction) {
        this.updateFunction = updateFunction;
    }

}


package eu.lestard.fluxfx;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * A base class for all Stores.
 */
public abstract class Store {

    private List<Runnable> updateListener = new ArrayList<>();

    /**
     * This method can be used to subscribe to actions of a given type. The subscription is managed by the {@link Dispatcher}.
     *
     * @param actionType the class type of the action.
     * @param actionConsumer a consumer function that is invoked when an action is dispatched.
     * @param <T> the generic type of the action.
     */
    protected <T extends Action> void subscribe(Class<T> actionType, Consumer<T> actionConsumer) {
        Dispatcher.getInstance().getActionStream(actionType).subscribe(actionConsumer);
    }

    protected void onChange() {
        updateListener.forEach(Runnable::run);
    }

    public void addOnChangeListener(Runnable listener) {
        this.updateListener.add(listener);
    }

    public void removeOnChangeListener(Runnable listener) {
        this.updateListener.remove(listener);
    }

}


/*
 * Copyright (c) 2004-2013 QOS.ch
 * All rights reserved.
 *
 * Permission is hereby granted, free  of charge, to any person obtaining
 * a  copy  of this  software  and  associated  documentation files  (the
 * "Software"), to  deal in  the Software without  restriction, including
 * without limitation  the rights to  use, copy, modify,  merge, publish,
 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
 * permit persons to whom the Software  is furnished to do so, subject to
 * the following conditions:
 *
 * The  above  copyright  notice  and  this permission  notice  shall  be
 * included in all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package org.slf4j.impl;

import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * TimberAndroidLoggerFactory is an implementation of {@link ILoggerFactory} returning
 * the appropriately named {@link TimberAndroidLoggerFactory} instance.
 *
 * @author Patrick Favre-Bulle <patrick.favrebulle@gmail.com>
 */
class TimberAndroidLoggerFactory implements ILoggerFactory {
    static final String ANONYMOUS_TAG = "null";

    private final ConcurrentMap<String, Logger> loggerMap = new ConcurrentHashMap<>();

    /**
     * Return an appropriate {@link TimberAndroidLoggerAdapter} instance by name.
     */
    public Logger getLogger(String name) {
        String tag = name == null ? ANONYMOUS_TAG : name;
        Logger logger = loggerMap.get(tag);
        if (logger == null) {
            Logger newInstance = new TimberAndroidLoggerAdapter(tag);
            Logger oldInstance = loggerMap.putIfAbsent(tag, newInstance);
            logger = oldInstance == null ? newInstance : oldInstance;
        }
        return logger;
    }
}


/*
 * Copyright (c) 2004-2013 QOS.ch
 * All rights reserved.
 *
 * Permission is hereby granted, free  of charge, to any person obtaining
 * a  copy  of this  software  and  associated  documentation files  (the
 * "Software"), to  deal in  the Software without  restriction, including
 * without limitation  the rights to  use, copy, modify,  merge, publish,
 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
 * permit persons to whom the Software  is furnished to do so, subject to
 * the following conditions:
 *
 * The  above  copyright  notice  and  this permission  notice  shall  be
 * included in all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package org.slf4j.impl;

import android.util.Log;

import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MarkerIgnoringBase;
import org.slf4j.helpers.MessageFormatter;

import timber.log.Timber;

/**
 *
A simple implementation that delegates all log requests to the Jake Wharton's Timber
 * logging facilities. Note that this logger does not support {@link org.slf4j.Marker}.
 * Methods taking marker data as parameter simply invoke the eponymous method
 * without the Marker argument, discarding any marker data in the process.

 *

 *
The logging levels specified for SLF4J can be almost directly mapped to
 * the levels that exist in Timber platform. The following table
 * shows the mapping implemented by this logger.

 *

 *

 *
SLF4J	Android

 *
TRACE	{@link android.util.Log#VERBOSE}

 *
DEBUG	{@link android.util.Log#DEBUG}

 *
INFO	{@link android.util.Log#INFO}

 *
WARN	{@link android.util.Log#WARN}

 *
ERROR	{@link android.util.Log#ERROR}

 *

 *

 *
Use loggers as usual:
 *

 *

 * Declare a logger

 * private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
 *

 *

 * Invoke logging methods, e.g.,

 * logger.debug("Some log message. Details: {}", someObject);

 * logger.debug("Some log message with varargs. Details: {}, {}, {}", someObject1, someObject2, someObject3);
 *

 *

 *

 *

 *
Logger instances created using the LoggerFactory are named either according to the name
 * or the fully qualified class name of the class given as a parameter.
 * Each logger name will be used as the log message tag on the Android platform. No truncating
 * will take place since Timber handles this itself.
 *

 *
 * @author Andrey Korzhevskiy
 * @author Patrick Favre-Bulle <patrick.favrebulle@gmail.com>
 */
class TimberAndroidLoggerAdapter extends MarkerIgnoringBase {
    private static final long serialVersionUID = -1227274521521287937L;

    /**
     * Package access allows only {@link TimberAndroidLoggerFactory} to instantiate
     * SimpleLogger instances.
     */
    TimberAndroidLoggerAdapter(String tag) {
        this.name = tag;
    }

    /**
     * Is this logger instance enabled for the VERBOSE level?
     *
     * @return True if this Logger is enabled for level VERBOSE, false otherwise.
     */
    public boolean isTraceEnabled() {
        return isLoggable(Log.VERBOSE);
    }

    /**
     * Log a message object at level VERBOSE.
     *
     * @param msg - the message object to be logged
     */
    public void trace(String msg) {
        log(Log.VERBOSE, msg, null);
    }

    /**
     * Log a message at level VERBOSE according to the specified format and
     * argument.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for level VERBOSE.
     *

     *
     * @param format the format string
     * @param arg    the argument
     */
    public void trace(String format, Object arg) {
        formatAndLog(Log.VERBOSE, format, arg);
    }

    /**
     * Log a message at level VERBOSE according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the VERBOSE level.
     *

     *
     * @param format the format string
     * @param arg1   the first argument
     * @param arg2   the second argument
     */
    public void trace(String format, Object arg1, Object arg2) {
        formatAndLog(Log.VERBOSE, format, arg1, arg2);
    }

    /**
     * Log a message at level VERBOSE according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the VERBOSE level.
     *

     *
     * @param format   the format string
     * @param argArray an array of arguments
     */
    public void trace(String format, Object... argArray) {
        formatAndLog(Log.VERBOSE, format, argArray);
    }

    /**
     * Log an exception (throwable) at level VERBOSE with an accompanying message.
     *
     * @param msg the message accompanying the exception
     * @param t   the exception (throwable) to log
     */
    public void trace(String msg, Throwable t) {
        log(Log.VERBOSE, msg, t);
    }

    /**
     * Is this logger instance enabled for the DEBUG level?
     *
     * @return True if this Logger is enabled for level DEBUG, false otherwise.
     */
    public boolean isDebugEnabled() {
        return isLoggable(Log.DEBUG);
    }

    /**
     * Log a message object at level DEBUG.
     *
     * @param msg - the message object to be logged
     */
    public void debug(String msg) {
        log(Log.DEBUG, msg, null);
    }

    /**
     * Log a message at level DEBUG according to the specified format and argument.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for level DEBUG.
     *

     *
     * @param format the format string
     * @param arg    the argument
     */
    public void debug(String format, Object arg) {
        formatAndLog(Log.DEBUG, format, arg);
    }

    /**
     * Log a message at level DEBUG according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the DEBUG level.
     *

     *
     * @param format the format string
     * @param arg1   the first argument
     * @param arg2   the second argument
     */
    public void debug(String format, Object arg1, Object arg2) {
        formatAndLog(Log.DEBUG, format, arg1, arg2);
    }

    /**
     * Log a message at level DEBUG according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the DEBUG level.
     *

     *
     * @param format   the format string
     * @param argArray an array of arguments
     */
    public void debug(String format, Object... argArray) {
        formatAndLog(Log.DEBUG, format, argArray);
    }

    /**
     * Log an exception (throwable) at level DEBUG with an accompanying message.
     *
     * @param msg the message accompanying the exception
     * @param t   the exception (throwable) to log
     */
    public void debug(String msg, Throwable t) {
        log(Log.DEBUG, msg, t);
    }

    /**
     * Is this logger instance enabled for the INFO level?
     *
     * @return True if this Logger is enabled for the INFO level, false otherwise.
     */
    public boolean isInfoEnabled() {
        return isLoggable(Log.INFO);
    }

    /**
     * Log a message object at the INFO level.
     *
     * @param msg - the message object to be logged
     */
    public void info(String msg) {
        log(Log.INFO, msg, null);
    }

    /**
     * Log a message at level INFO according to the specified format and argument.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the INFO level.
     *

     *
     * @param format the format string
     * @param arg    the argument
     */
    public void info(String format, Object arg) {
        formatAndLog(Log.INFO, format, arg);
    }

    /**
     * Log a message at the INFO level according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the INFO level.
     *

     *
     * @param format the format string
     * @param arg1   the first argument
     * @param arg2   the second argument
     */
    public void info(String format, Object arg1, Object arg2) {
        formatAndLog(Log.INFO, format, arg1, arg2);
    }

    /**
     * Log a message at level INFO according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the INFO level.
     *

     *
     * @param format   the format string
     * @param argArray an array of arguments
     */
    public void info(String format, Object... argArray) {
        formatAndLog(Log.INFO, format, argArray);
    }

    /**
     * Log an exception (throwable) at the INFO level with an accompanying
     * message.
     *
     * @param msg the message accompanying the exception
     * @param t   the exception (throwable) to log
     */
    public void info(String msg, Throwable t) {
        log(Log.INFO, msg, t);
    }

    /**
     * Is this logger instance enabled for the WARN level?
     *
     * @return True if this Logger is enabled for the WARN level, false
     * otherwise.
     */
    public boolean isWarnEnabled() {
        return isLoggable(Log.WARN);
    }

    /**
     * Log a message object at the WARN level.
     *
     * @param msg - the message object to be logged
     */
    public void warn(String msg) {
        log(Log.WARN, msg, null);
    }

    /**
     * Log a message at the WARN level according to the specified format and
     * argument.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the WARN level.
     *

     *
     * @param format the format string
     * @param arg    the argument
     */
    public void warn(String format, Object arg) {
        formatAndLog(Log.WARN, format, arg);
    }

    /**
     * Log a message at the WARN level according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the WARN level.
     *

     *
     * @param format the format string
     * @param arg1   the first argument
     * @param arg2   the second argument
     */
    public void warn(String format, Object arg1, Object arg2) {
        formatAndLog(Log.WARN, format, arg1, arg2);
    }

    /**
     * Log a message at level WARN according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the WARN level.
     *

     *
     * @param format   the format string
     * @param argArray an array of arguments
     */
    public void warn(String format, Object... argArray) {
        formatAndLog(Log.WARN, format, argArray);
    }

    /**
     * Log an exception (throwable) at the WARN level with an accompanying
     * message.
     *
     * @param msg the message accompanying the exception
     * @param t   the exception (throwable) to log
     */
    public void warn(String msg, Throwable t) {
        log(Log.WARN, msg, t);
    }

    /**
     * Is this logger instance enabled for level ERROR?
     *
     * @return True if this Logger is enabled for level ERROR, false otherwise.
     */
    public boolean isErrorEnabled() {
        return isLoggable(Log.ERROR);
    }

    /**
     * Log a message object at the ERROR level.
     *
     * @param msg - the message object to be logged
     */
    public void error(String msg) {
        log(Log.ERROR, msg, null);
    }

    /**
     * Log a message at the ERROR level according to the specified format and
     * argument.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the ERROR level.
     *

     *
     * @param format the format string
     * @param arg    the argument
     */
    public void error(String format, Object arg) {
        formatAndLog(Log.ERROR, format, arg);
    }

    /**
     * Log a message at the ERROR level according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the ERROR level.
     *

     *
     * @param format the format string
     * @param arg1   the first argument
     * @param arg2   the second argument
     */
    public void error(String format, Object arg1, Object arg2) {
        formatAndLog(Log.ERROR, format, arg1, arg2);
    }

    /**
     * Log a message at level ERROR according to the specified format and
     * arguments.
     *

     *

     * This form avoids superfluous object creation when the logger is disabled
     * for the ERROR level.
     *

     *
     * @param format   the format string
     * @param argArray an array of arguments
     */
    public void error(String format, Object... argArray) {
        formatAndLog(Log.ERROR, format, argArray);
    }

    /**
     * Log an exception (throwable) at the ERROR level with an accompanying
     * message.
     *
     * @param msg the message accompanying the exception
     * @param t   the exception (throwable) to log
     */
    public void error(String msg, Throwable t) {
        log(Log.ERROR, msg, t);
    }

    private void formatAndLog(int priority, String format, Object... argArray) {
        if (isLoggable(priority)) {
            FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
            logInternal(priority, ft.getMessage(), ft.getThrowable());
        }
    }

    private void log(int priority, String message, Throwable throwable) {
        if (isLoggable(priority)) {
            logInternal(priority, message, throwable);
        }
    }

    /**
     * Currently all levels are enabled, since Timber will decide if the log will be actually used
     *
     * @return always true
     */
    private boolean isLoggable(int priority) {
        return Timber.treeCount() > 0;
    }

    private void logInternal(int priority, String message, Throwable throwable) {
        Timber.tag(name);

        if (throwable != null) {
            if (message != null) {
                Timber.log(priority, throwable, message);
            } else {
                Timber.log(priority, throwable);
            }
        } else {
            Timber.log(priority, message);
        }
    }
}


package com.codepoetics.octarine.joins;

import com.codepoetics.octarine.functional.tuples.T2;
import com.codepoetics.octarine.records.ListKey;
import com.codepoetics.octarine.records.Record;

import java.util.Collection;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

public final class RecordJoiner<K> {

    private final Index<K, Record> leftIndex;
    private final JoinKey<Record, K> primaryKey;

    public RecordJoiner(Index<K, Record> leftIndex, JoinKey<Record, K> primaryKey) {
        this.leftIndex = leftIndex;
        this.primaryKey = primaryKey;
    }

    // Many to one
    public Stream<Record> manyToOne(Collection<? extends Record> rights) {
        return manyToOne(rights.stream());
    }

    public Stream<Record> manyToOne(Fetcher<K, Record> fetcher) {
        return manyToOne(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<Record> manyToOne(Stream<? extends Record> rights) {
        return leftIndex.manyToOne(primaryKey.index(rights)).map(recordIntoRecord());
    }

    // Strict many to one
    public Stream<Record> strictManyToOne(Collection<? extends Record> rights) {
        return strictManyToOne(rights.stream());
    }

    public Stream<Record> strictManyToOne(Fetcher<K, Record> fetcher) {
        return strictManyToOne(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<Record> strictManyToOne(Stream<? extends Record> rights) {
        return leftIndex.strictManyToOne(primaryKey.index(rights)).map(recordIntoRecord());
    }

    // One to many
    public Stream<Record> oneToMany(Collection<? extends Record> rights, ListKey<Record> manyKey) {
        return oneToMany(rights.stream(), manyKey);
    }

    public Stream<Record> oneToMany(Fetcher<K, Record> fetcher, ListKey<Record> manyKey) {
        return oneToMany(fetcher.fetch(leftIndex.keys()), manyKey);
    }

    public Stream<Record> oneToMany(Stream<? extends Record> rights, ListKey<Record> manyKey) {
        return leftIndex.oneToMany(primaryKey.index(rights)).map(recordsIntoRecord(manyKey));
    }

    // Strict one to many
    public Stream<Record> strictOneToMany(Collection<? extends Record> rights, ListKey<Record> manyKey) {
        return strictOneToMany(rights.stream(), manyKey);
    }

    public Stream<Record> strictOneToMany(Fetcher<K, Record> fetcher, ListKey<Record> manyKey) {
        return strictOneToMany(fetcher.fetch(leftIndex.keys()), manyKey);
    }

    public Stream<Record> strictOneToMany(Stream<? extends Record> rights, ListKey<Record> manyKey) {
        return leftIndex.strictOneToMany(primaryKey.index(rights)).map(recordsIntoRecord(manyKey));
    }

    // Strict one to one
    public Stream<Record> strictOneToOne(Collection<? extends Record> rights) {
        return strictOneToOne(rights.stream());
    }

    public Stream<Record> strictOneToOne(Fetcher<K, Record> fetcher) {
        return strictOneToOne(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<Record> strictOneToOne(Stream<? extends Record> rights) {
        return leftIndex.strictOneToOne(primaryKey.index(rights)).map(recordIntoRecord());
    }

    private Function<T2<Record, Record>, Record> recordIntoRecord() {
        return t -> t.pack(Record::with);
    }

    private Function<T2<Record, Set<Record>>, Record> recordsIntoRecord(ListKey<Record> key) {
        return t -> t.pack((f, s) -> f.with(key.of(s)));
    }
}


package com.codepoetics.octarine.joins;

import com.codepoetics.octarine.functional.tuples.T2;

import java.util.Collection;
import java.util.Set;
import java.util.stream.Stream;

public final class Joiner<L, R, K> {

    private final Index<K, L> leftIndex;
    private final JoinKey<R, K> primaryKey;

    public Joiner(Index<K, L> leftIndex, JoinKey<R, K> primaryKey) {
        this.leftIndex = leftIndex;
        this.primaryKey = primaryKey;
    }

    // Many to one
    public Stream<T2<L, R>> manyToOne(Collection<? extends R> rights) {
        return manyToOne(rights.stream());
    }

    public Stream<T2<L, R>> manyToOne(Fetcher<K, R> fetcher) {
        return manyToOne(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<T2<L, R>> manyToOne(Stream<? extends R> rights) {
        return leftIndex.manyToOne(primaryKey.index(rights));
    }

    // Strict many to one
    public Stream<T2<L, R>> strictManyToOne(Collection<? extends R> rights) {
        return strictManyToOne(rights.stream());
    }

    public Stream<T2<L, R>> strictManyToOne(Fetcher<K, R> fetcher) {
        return strictManyToOne(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<T2<L, R>> strictManyToOne(Stream<? extends R> rights) {
        return leftIndex.strictManyToOne(primaryKey.index(rights));
    }

    // One to many
    public Stream<T2<L, Set<R>>> oneToMany(Collection<? extends R> rights) {
        return oneToMany(rights.stream());
    }

    public Stream<T2<L, Set<R>>> oneToMany(Fetcher<K, R> fetcher) {
        return oneToMany(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<T2<L, Set<R>>> oneToMany(Stream<? extends R> rights) {
        return leftIndex.oneToMany(primaryKey.index(rights));
    }

    // Strict one to many
    public Stream<T2<L, Set<R>>> strictOneToMany(Collection<? extends R> rights) {
        return strictOneToMany(rights.stream());
    }

    public Stream<T2<L, Set<R>>> strictOneToMany(Fetcher<K, R> fetcher) {
        return strictOneToMany(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<T2<L, Set<R>>> strictOneToMany(Stream<? extends R> rights) {
        return leftIndex.strictOneToMany(primaryKey.index(rights));
    }

    // Strict one to one
    public Stream<T2<L, R>> strictOneToOne(Collection<? extends R> rights) {
        return strictOneToOne(rights.stream());
    }

    public Stream<T2<L, R>> strictOneToOne(Fetcher<K, R> fetcher) {
        return strictOneToOne(fetcher.fetch(leftIndex.keys()));
    }

    public Stream<T2<L, R>> strictOneToOne(Stream<? extends R> rights) {
        return leftIndex.strictOneToOne(primaryKey.index(rights));
    }

}


package com.tradier.raven.logging;

import static org.slf4j.Logger.ROOT_LOGGER_NAME;
import io.dropwizard.logging.async.AsyncLoggingEventAppenderFactory;
import io.dropwizard.logging.filter.ThresholdLevelFilterFactory;
import io.dropwizard.logging.layout.DropwizardLayoutFactory;

import java.util.Optional;

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;

/**
 * A class adding a configured {@link com.getsentry.raven.logback.SentryAppender} to the root
 * logger.
 */
public final class RavenBootstrap {
  private RavenBootstrap() { /* No instance methods */ }

  /**
   * Bootstrap the SLF4J root logger with a configured
   * {@link com.getsentry.raven.logback.SentryAppender}.
   *
   * @param dsn The DSN (Data Source Name) for your project
   */
  public static void bootstrap(final String dsn) {
    bootstrap(dsn, true);
  }

  /**
   * Bootstrap the SLF4J root logger with a configured
   * {@link com.getsentry.raven.logback.SentryAppender}.
   *
   * @param dsn The DSN (Data Source Name) for your project
   * @param cleanRootLogger If true, detach and stop all other appenders from the root logger
   */
  public static void bootstrap(final String dsn, boolean cleanRootLogger) {
    bootstrap(dsn, Optional.empty(), cleanRootLogger);
  }

  /**
   * Bootstrap the SLF4J root logger with a configured
   * {@link com.getsentry.raven.logback.SentryAppender}.
   *
   * @param dsn The DSN (Data Source Name) for your project
   * @param tags Custom tags to send to Sentry along with errors
   * @param cleanRootLogger If true, detach and stop all other appenders from the root logger
   */
  public static void bootstrap(final String dsn, Optional<String> tags, boolean cleanRootLogger) {
    bootstrap(dsn, tags, Optional.empty(), Optional.empty(), cleanRootLogger);
  }

  /**
   * Bootstrap the SLF4J root logger with a configured
   * {@link com.getsentry.raven.logback.SentryAppender}.
   *
   * @param dsn The DSN (Data Source Name) for your project
   * @param tags Custom tags to send to Sentry along with errors
   * @param environment The environment name to pass to Sentry
   * @param release The release name to pass to Sentry
   * @param cleanRootLogger If true, detach and stop all other appenders from the root logger
   */
  public static void bootstrap(final String dsn, Optional<String> tags,
      Optional<String> environment, Optional<String> release, boolean cleanRootLogger) {
    bootstrap(dsn, tags, environment, release, Optional.empty(), cleanRootLogger);
  }

  /**
   * Bootstrap the SLF4J root logger with a configured
   * {@link com.getsentry.raven.logback.SentryAppender}.
   *
   * @param dsn The DSN (Data Source Name) for your project
   * @param tags Custom tags to send to Sentry along with errors
   * @param environment The environment name to pass to Sentry
   * @param release The release name to pass to Sentry
   * @param serverName The server name to pass to Sentry
   * @param cleanRootLogger If true, detach and stop all other appenders from the root logger
   */
  public static void bootstrap(final String dsn, Optional<String> tags,
      Optional<String> environment, Optional<String> release, Optional<String> serverName,
      boolean cleanRootLogger) {
    final RavenAppenderFactory raven = new RavenAppenderFactory();
    raven.setThreshold(Level.ERROR);
    raven.setDsn(dsn);
    raven.setTags(tags);
    raven.setEnvironment(environment);
    raven.setRelease(release);
    raven.setServerName(serverName);

    registerAppender(dsn, cleanRootLogger, raven);
  }

  private static void registerAppender(String dsn, boolean cleanRootLogger,
      RavenAppenderFactory raven) {
    final Logger root = (Logger) LoggerFactory.getLogger(ROOT_LOGGER_NAME);

    if (cleanRootLogger) {
      root.detachAndStopAllAppenders();
    }

    final ThresholdLevelFilterFactory levelFilterFactory = new ThresholdLevelFilterFactory();
    final DropwizardLayoutFactory layoutFactory = new DropwizardLayoutFactory();
    final AsyncLoggingEventAppenderFactory asyncAppenderFactory =
        new AsyncLoggingEventAppenderFactory();
    root.addAppender(raven.build(root.getLoggerContext(), dsn, layoutFactory, levelFilterFactory,
        asyncAppenderFactory));
  }
}