/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * 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 com.ocpsoft.pretty.faces.config.dynaview;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.context.FacesContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ocpsoft.pretty.PrettyContext;
import com.ocpsoft.pretty.PrettyException;
import com.ocpsoft.pretty.faces.application.PrettyRedirector;
import com.ocpsoft.pretty.faces.beans.ExtractedValuesURLBuilder;
import com.ocpsoft.pretty.faces.config.mapping.UrlMapping;
import com.ocpsoft.pretty.faces.util.FacesElUtils;

public class DynaviewEngine
{
   public static final String DYNAVIEW = "com.ocpsoft.dynaView";

   private static final Log log = LogFactory.getLog(DynaviewEngine.class);
   private static FacesElUtils elUtils = new FacesElUtils();

   /**
    * Given the string value of the Faces Servlet mapping, return a string that is guaranteed to match when a servlet
    * forward is issued. It doesn't matter which FacesServlet we get to, as long as we get to one.
    */
   public String buildDynaViewId(final String facesServletMapping)
   {
      StringBuffer result = new StringBuffer();

      Map<Pattern, String> patterns = new LinkedHashMap<Pattern, String>();

      Pattern pathMapping = Pattern.compile("^(/.*/)\\*$");
      Pattern extensionMapping = Pattern.compile("^\\*(\\..*)$");
      Pattern defaultMapping = Pattern.compile("^/$");

      patterns.put(pathMapping, "$1" + DYNAVIEW + ".jsf");
      patterns.put(extensionMapping, "/" + DYNAVIEW + "$1");
      patterns.put(defaultMapping, "/" + DYNAVIEW + ".jsf");

      boolean matched = false;
      Iterator<Pattern> iterator = patterns.keySet().iterator();
      while ((matched == false) && iterator.hasNext())
      {
         Pattern p = iterator.next();
         Matcher m = p.matcher(facesServletMapping);
         if (m.matches())
         {
            String replacement = patterns.get(p);
            m.appendReplacement(result, replacement);
            matched = true;
         }
      }

      if (matched == false)
      {
         // This is an exact url-mapping, use it.
         result.append(facesServletMapping);
      }

      return result.toString();
   }

   /**
    * Handle DynaView processing. This method will end the Faces life-cycle.
    */
   public void processDynaView(final PrettyContext prettyContext, final FacesContext facesContext)
   {
      log.trace("Requesting DynaView processing for: " + prettyContext.getRequestURL());
      String viewId = "";
      try
      {
         viewId = prettyContext.getCurrentViewId();
         log.trace("Invoking DynaView method: " + viewId);
         Object result = computeDynaViewId(facesContext);
         if (result instanceof String)
         {
            viewId = (String) result;
            log.trace("Forwarding to DynaView: " + viewId);
            prettyContext.setDynaviewProcessed(true);
            facesContext.getExternalContext().dispatch(viewId);
            facesContext.responseComplete();
         }
      }
      catch (Exception e)
      {
         log.error("Failed to process dynaview", e);
         PrettyRedirector prettyRedirector = new PrettyRedirector();
         prettyRedirector.send404(facesContext);
         throw new PrettyException("Could not forward to view: " + viewId + "", e);
      }
   }

   /**
    * Calculate the Faces ViewId to which this request URI resolves. This method will recursively call any dynamic
    * mapping viewId functions as needed until a String viewId is returned, or supplied by a static mapping.
    *

    * This phase does not support FacesNavigation or PrettyRedirecting. Its SOLE purpose is to resolve a viewId.
    *

    * Note: Precondition - parameter injection must take place before this
    *

    * Postcondition - currentViewId is set to computed View Id
    *
    * @param facesContext2
    *
    * @return JSF viewID to which this request resolves.
    */
   public String computeDynaViewId(final FacesContext facesContext)
   {
      PrettyContext context = PrettyContext.getCurrentInstance(facesContext);
      UrlMapping urlMapping = context.getCurrentMapping();

      return calculateDynaviewId(facesContext, urlMapping);
   }

   public String calculateDynaviewId(final FacesContext facesContext, UrlMapping urlMapping)
   {
      String result = "";
      if (urlMapping != null)
      {
         PrettyContext context = PrettyContext.getCurrentInstance(facesContext);

         String viewId = urlMapping.getViewId();
         if (viewId == null)
         {
            viewId = "";
         }
         while (elUtils.isEl(viewId))
         {
            Object viewResult = elUtils.invokeMethod(facesContext, viewId);
            if (viewResult == null)
            {
               viewId = "";
               break;
            }
            else
            {
               viewId = viewResult.toString();
            }

            if (context.getConfig().isMappingId(viewId))
            {
               urlMapping = context.getConfig().getMappingById(viewId);
               viewId = urlMapping.getViewId();
               ExtractedValuesURLBuilder builder = new ExtractedValuesURLBuilder();
               result = context.getContextPath() + builder.buildURL(urlMapping).encode()
                         + builder.buildQueryString(urlMapping);
            }
            else
            {
               result = viewId;
            }
         }
         if ("".equals(viewId))
         {
            log.debug("ViewId for mapping with id <" + urlMapping.getId() + "> was blank");
         }
         result = context.stripContextPath(result);
      }
      return result;
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.ocpsoft.rewrite.config;

import org.ocpsoft.rewrite.util.Visitor;

/**
 * This class allows {@link Visitor}s to walk through a {@link Condition} tree which may contain
 * {@link CompositeCondition} elements.
 *
 * @author Christian Kaltepoth
 */
public class ConditionVisit
{

   private final Condition root;

   /**
    * Initialize class with the supplied root {@link Condition}
    */
   public ConditionVisit(Condition root)
   {
      this.root = root;
   }

   /**
    * Submit the supplied visitor and apply it to all {@link Condition} instances in the tree
    */
   public void accept(Visitor<Condition> visitor)
   {
      visit(root, visitor);
   }

   /**
    * Method to call the visitor for the supplied {@link Condition} and recursively calls itself, if the
    * {@link Condition} is a {@link CompositeCondition}.
    */
   private void visit(Condition condition, Visitor<Condition> visitor)
   {
      visitor.visit(condition);

      if (condition instanceof CompositeCondition) {
         if (((CompositeCondition) condition).getConditions() != null) {
            for (Condition child : ((CompositeCondition) condition).getConditions()) {
               visit(child, visitor);
            }
         }
      }
   }

}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.ocpsoft.rewrite.config;

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

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.common.util.Strings;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;

/**
 * Evaluates all provided {@link Operation} instances. If all provided operations return true, this
 * operation returns true. If any provided operations return false, this operation returns
 * false.
 *
 * @author Lincoln Baxter, III
 */
public final class Perform extends DefaultOperationBuilder implements CompositeOperation
{
   private final Operation[] operations;

   private Perform(final Operation... operations)
   {
      this.operations = operations;
   }

   /**
    * Creates a new {@link Perform} operation. If all provided {@link Operation} instances return true,
    * this operation returns true. If any provided operations return false, this operation
    * returns false.
    *
    * @param operations the array of operations to be evaluated
    */
   public static Perform all(final Operation... operations)
   {
      Assert.notNull(operations, "At least one operation is required.");
      Assert.assertTrue(operations.length > 0, "At least one operation is required.");
      return new Perform(flattenOperations(Arrays.asList(operations)).toArray(new Operation[] {}));
   }

   private static List<Operation> flattenOperations(List<Operation> operations)
   {
      List<Operation> result = new ArrayList<Operation>();
      for (Operation operation : operations) {
         if (operation instanceof Perform)
         {
            result.addAll(flattenOperations(((Perform) operation).getOperations()));
         }
         else
         {
            result.add(operation);
         }
      }
      return result;
   }

   @Override
   public void perform(Rewrite event, EvaluationContext context)
   {
      for (int i = 0; i < operations.length; i++) {
         operations[i].perform(event, context);
      }
   }

   @Override
   public List<Operation> getOperations()
   {
      return Arrays.asList(operations);
   }

   @Override
   public String toString()
   {
      return "Perform.all(" + Strings.join(getOperations(), ", ") + ")";
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.ocpsoft.rewrite.cdi;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import javax.enterprise.inject.Any;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.util.AnnotationLiteral;

import org.ocpsoft.common.spi.ServiceLocator;
import org.ocpsoft.rewrite.cdi.manager.BeanManagerAware;
import org.ocpsoft.rewrite.cdi.util.ParameterizedTypeImpl;
import org.ocpsoft.rewrite.cdi.util.WildcardTypeImpl;

/**
 * @author Lincoln Baxter, III
 * @author Christian Kaltepoth
 */
public class CdiServiceLocator extends BeanManagerAware implements ServiceLocator
{

   @Override
   @SuppressWarnings("unchecked")
   public <T> Collection<Class<T>> locate(final Class<T> type)
   {
      List<Class<T>> result = new ArrayList<Class<T>>();

      // the BeanManager may be not available during Rewrite startup
      if (isBeanManagerAvailable()) {

         BeanManager manager = getBeanManager();

         Set<Bean<?>> beans = manager.getBeans(getRequiredType(type), new Annotation[] { new AnnotationLiteral<Any>() {
            private static final long serialVersionUID = -1896831901770051851L;
         } });

         for (Bean<?> bean : beans) {
            result.add((Class<T>) bean.getBeanClass());
         }

      }

      return result;

   }

   /**
    * Builds the correct "required type" including actual type arguments in case of parameterized types.
    *
    * @see https://github.com/ocpsoft/rewrite/issues/137
    */
   private Type getRequiredType(Class<?> clazz)
   {

      TypeVariable<?>[] typeParameters = clazz.getTypeParameters();

      if (typeParameters.length > 0) {

         Type[] actualTypeParameters = new Type[typeParameters.length];
         for (int i = 0; i < typeParameters.length; i++) {
            actualTypeParameters[i] = new WildcardTypeImpl(new Type[] { Object.class }, new Type[] {});
         }

         return new ParameterizedTypeImpl(clazz, actualTypeParameters, null);

      }

      return clazz;
   }

}


/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * 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 com.ocpsoft.pretty.faces.event;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import com.ocpsoft.pretty.faces.util.FacesMessagesUtils;

/**
 * @author Lincoln Baxter, III <lincoln@ocpsoft.com>
 */
public class MultiPageMessagesSupport implements PhaseListener
{

   private static final long serialVersionUID = 1250469273857785274L;
   private final FacesMessagesUtils messagesUtils = new FacesMessagesUtils();

   public PhaseId getPhaseId()
   {
      return PhaseId.ANY_PHASE;
   }

   public void beforePhase(final PhaseEvent event)
   {
      FacesContext facesContext = event.getFacesContext();
      messagesUtils.saveMessages(facesContext, facesContext.getExternalContext().getSessionMap());

      if (PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
      {
         /*
          * Check to see if we are "naturally" in the RENDER_RESPONSE phase. If
          * we have arrived here and the response is already complete, then the
          * page is not going to show up: don't display messages yet.
          */
         if (!facesContext.getResponseComplete())
         {
            messagesUtils.restoreMessages(facesContext, facesContext.getExternalContext().getSessionMap());
         }
      }
   }

   /*
    * Save messages into the session after every phase.
    */
   public void afterPhase(final PhaseEvent event)
   {
      if (!PhaseId.RENDER_RESPONSE.equals(event.getPhaseId()))
      {
         FacesContext facesContext = event.getFacesContext();
         messagesUtils.saveMessages(facesContext, facesContext.getExternalContext().getSessionMap());
      }
   }
}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof.annotations;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Allows you to transform the type of your custom annotation to a reference annotation type.
 * It can come handy when you want to allow the consumers of your library not to depend on your API because of the annotations, still allowing them to use the original annotation methods.
 *

 * Example :
 *

 * //getting a custom annotation from a class
 * my.Annotation customAnnotation = klazz.getAnnotation(my.Annotation.class);
 * //if this annotation is "similar" (duck-typing, same methods) to the reference one, I can get a proxy to it, whose type is the reference annotation
 * ehcache.Annotation annotation = AnnotationProxyFactory.getAnnotationProxy(customAnnotation, ehcache.Annotation.class);
 *

 * //so my library can apply the behavior when the default annotation is used
 *
 * @author Anthony Dahanne
 * @ehcache.Annotation(action="true") public class UserClass {}
 *

 * //or when a custom one is used, since all calls to action() will be caught and redirected to the custom annotation action method, if it exists,
 * //or fall back to the reference action method
 * @my.Annotation(action="true") public class UserClass {}
 */
public final class AnnotationProxyFactory {

    private AnnotationProxyFactory() {
        //not to instantiate
    }

    /**
     * Returns a proxy on the customAnnotation, having the same type than the referenceAnnotation
     *
     * @param customAnnotation annotation proxied
     * @param referenceAnnotation type of the returned annotation
     * @return proxied customAnnotation with the type of referenceAnnotation
     */
    @SuppressWarnings("unchecked")
    public static <T extends Annotation> T getAnnotationProxy(Annotation customAnnotation, Class<T> referenceAnnotation) {
        InvocationHandler handler = new AnnotationInvocationHandler(customAnnotation);
        return (T)Proxy.newProxyInstance(referenceAnnotation.getClassLoader(), new Class[] { referenceAnnotation }, handler);
    }

    /**
     * Invocation handler implementing an invoke method that redirects every method call to the custom annotation method
     * when possible; if not returns the reference annotation method default value
     */
    private static class AnnotationInvocationHandler implements InvocationHandler {

        private final Annotation customAnnotation;

        public AnnotationInvocationHandler(Annotation customAnnotation) {
            this.customAnnotation = customAnnotation;
        }

        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
            //trying to call the method on the custom annotation, if it exists
            Method methodOnCustom = getMatchingMethodOnGivenAnnotation(method);
            if (methodOnCustom != null) {
                return methodOnCustom.invoke(customAnnotation, args);
            } else {
                //otherwise getting the default value of the reference annotation method
                Object defaultValue = method.getDefaultValue();
                if (defaultValue != null) {
                    return defaultValue;
                }
                throw new UnsupportedOperationException(
                    "The method \""
                    + method.getName()
                    + "\" does not exist in the custom annotation, and there is no default value for"
                    + " it in the reference annotation, please implement this method in your custom annotation.");
            }
        }

        private Method getMatchingMethodOnGivenAnnotation(Method method) {
            try {
                Method customMethod = customAnnotation.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
                if (customMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
                    return customMethod;
                }
                return null;
            } catch (NoSuchMethodException e) {
                return null;
            }
        }
    }

}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof;

import org.ehcache.sizeof.filters.SizeOfFilter;
import org.ehcache.sizeof.util.WeakIdentityConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.Set;

import static java.util.Collections.newSetFromMap;

/**
 * This will walk an object graph and let you execute some "function" along the way
 *
 * @author Alex Snaps
 */
final class ObjectGraphWalker {

    private static final Logger LOG = LoggerFactory.getLogger(ObjectGraphWalker.class);
    private static final String VERBOSE_DEBUG_LOGGING = "org.ehcache.sizeof.verboseDebugLogging";

    private static final String CONTINUE_MESSAGE =
        "The configured limit of {0} object references was reached while attempting to calculate the size of the object graph." +
        " Severe performance degradation could occur if the sizing operation continues. This can be avoided by setting the CacheManger" +
        " or Cache <sizeOfPolicy> element's maxDepthExceededBehavior to \"abort\" or adding stop points with @IgnoreSizeOf annotations." +
        " If performance degradation is NOT an issue at the configured limit, raise the limit value using the CacheManager or Cache" +
        " <sizeOfPolicy> element's maxDepth attribute. For more information, see the Ehcache configuration documentation.";

    private static final String ABORT_MESSAGE =
        "The configured limit of {0} object references was reached while attempting to calculate the size of the object graph." +
        " This can be avoided by adding stop points with @IgnoreSizeOf annotations. Since the CacheManger or Cache <sizeOfPolicy>" +
        " element's maxDepthExceededBehavior is set to \"abort\", the sizing operation has stopped and the reported cache size is not" +
        " accurate. If performance degradation is NOT an issue at the configured limit, raise the limit value using the CacheManager" +
        " or Cache <sizeOfPolicy> element's maxDepth attribute. For more information, see the Ehcache configuration documentation.";

    private static final boolean USE_VERBOSE_DEBUG_LOGGING;

    private final WeakIdentityConcurrentMap<Class<?>, SoftReference<Collection<Field>>> fieldCache =
        new WeakIdentityConcurrentMap<>();
    private final WeakIdentityConcurrentMap<Class<?>, Boolean> classCache =
        new WeakIdentityConcurrentMap<>();

    private final boolean bypassFlyweight;
    private final SizeOfFilter sizeOfFilter;

    private final Visitor visitor;

    static {
        USE_VERBOSE_DEBUG_LOGGING = getVerboseSizeOfDebugLogging();
    }

    /**
     * Constructor
     *
     * @param visitor the visitor to use
     * @param filter  the filtering
     * @param bypassFlyweight  the filtering
     * @see Visitor
     * @see SizeOfFilter
     */
    ObjectGraphWalker(Visitor visitor, SizeOfFilter filter, final boolean bypassFlyweight) {
        if(visitor == null) {
            throw new NullPointerException("Visitor can't be null");
        }
        if(filter == null) {
            throw new NullPointerException("SizeOfFilter can't be null");
        }
        this.visitor = visitor;
        this.sizeOfFilter = filter;
        this.bypassFlyweight = bypassFlyweight;
    }

    private static boolean getVerboseSizeOfDebugLogging() {

        String verboseString = System.getProperty(VERBOSE_DEBUG_LOGGING, "false").toLowerCase();

        return verboseString.equals("true");
    }

    /**
     * The visitor to execute the function on each node of the graph
     * This is only to be used for the sizing of an object graph in memory!
     */
    interface Visitor {
        /**
         * The visit method executed on each node
         *
         * @param object the reference at that node
         * @return a long for you to do things with...
         */
        long visit(Object object);
    }

    /**
     * Walk the graph and call into the "visitor"
     *
     * @param root                      the roots of the objects (a shared graph will only be visited once)
     * @return the sum of all Visitor#visit returned values
     */
    long walk(Object... root) {
        return walk(null, root);
    }

    /**
     * Walk the graph and call into the "visitor"
     *
     * @param visitorListener          A decorator for the Visitor
     * @param root                      the roots of the objects (a shared graph will only be visited once)
     * @return the sum of all Visitor#visit returned values
     */
    long walk(VisitorListener visitorListener, Object... root) {
        final StringBuilder traversalDebugMessage;
        if (USE_VERBOSE_DEBUG_LOGGING && LOG.isDebugEnabled()) {
            traversalDebugMessage = new StringBuilder();
        } else {
            traversalDebugMessage = null;
        }
        long result = 0;
        Deque<Object> toVisit = new ArrayDeque<>();
        Set<Object> visited = newSetFromMap(new IdentityHashMap<>());

        if (root != null) {
            if (traversalDebugMessage != null) {
                traversalDebugMessage.append("visiting ");
            }
            for (Object object : root) {
                nullSafeAdd(toVisit, object);
                if (traversalDebugMessage != null && object != null) {
                    traversalDebugMessage.append(object.getClass().getName())
                        .append("@").append(System.identityHashCode(object)).append(", ");
                }
            }
            if (traversalDebugMessage != null) {
                traversalDebugMessage.deleteCharAt(traversalDebugMessage.length() - 2).append("\n");
            }
        }

        while (!toVisit.isEmpty()) {

            Object ref = toVisit.pop();

            if (visited.add(ref)) {
                Class<?> refClass = ref.getClass();
                if (!byPassIfFlyweight(ref) && shouldWalkClass(refClass)) {
                    if (refClass.isArray() && !refClass.getComponentType().isPrimitive()) {
                        for (int i = 0; i < Array.getLength(ref); i++) {
                            nullSafeAdd(toVisit, Array.get(ref, i));
                        }
                    } else {
                        for (Field field : getFilteredFields(refClass)) {
                            try {
                                nullSafeAdd(toVisit, field.get(ref));
                            } catch (IllegalAccessException ex) {
                                throw new RuntimeException(ex);
                            }
                        }
                    }

                    final long visitSize = visitor.visit(ref);
                    if (visitorListener != null) {
                        visitorListener.visited(ref, visitSize);
                    }
                    if (traversalDebugMessage != null) {
                        traversalDebugMessage.append("  ").append(visitSize).append("b\t\t")
                            .append(ref.getClass().getName()).append("@").append(System.identityHashCode(ref)).append("\n");
                    }
                    result += visitSize;
                } else if (traversalDebugMessage != null) {
                    traversalDebugMessage.append("  ignored\t")
                        .append(ref.getClass().getName()).append("@").append(System.identityHashCode(ref)).append("\n");
                }
            }
        }

        if (traversalDebugMessage != null) {
            traversalDebugMessage.append("Total size: ").append(result).append(" bytes\n");
            LOG.debug(traversalDebugMessage.toString());
        }
        return result;
    }

    /**
     * Returns the filtered fields for a particular type
     *
     * @param refClass the type
     * @return A collection of fields to be visited
     */
    private Collection<Field> getFilteredFields(Class<?> refClass) {
        SoftReference<Collection<Field>> ref = fieldCache.get(refClass);
        Collection<Field> fieldList = ref != null ? ref.get() : null;
        if (fieldList != null) {
            return fieldList;
        } else {
            Collection<Field> result;
            result = sizeOfFilter.filterFields(refClass, getAllFields(refClass));
            if (USE_VERBOSE_DEBUG_LOGGING && LOG.isDebugEnabled()) {
                for (Field field : result) {
                    if (Modifier.isTransient(field.getModifiers())) {
                        LOG.debug("SizeOf engine walking transient field '{}' of class {}", field.getName(), refClass.getName());
                    }
                }
            }
            fieldCache.put(refClass, new SoftReference<>(result));
            return result;
        }
    }

    private boolean shouldWalkClass(Class<?> refClass) {
        Boolean cached = classCache.get(refClass);
        if (cached == null) {
            cached = sizeOfFilter.filterClass(refClass);
            classCache.put(refClass, cached);
        }
        return cached;
    }

    private static void nullSafeAdd(final Deque<Object> toVisit, final Object o) {
        if (o != null) {
            toVisit.push(o);
        }
    }

    /**
     * Returns all non-primitive fields for the entire class hierarchy of a type
     *
     * @param refClass the type
     * @return all fields for that type
     */
    private static Collection<Field> getAllFields(Class<?> refClass) {
        Collection<Field> fields = new ArrayList<>();
        for (Class<?> klazz = refClass; klazz != null; klazz = klazz.getSuperclass()) {
            for (Field field : klazz.getDeclaredFields()) {
                if (!Modifier.isStatic(field.getModifiers()) &&
                    !field.getType().isPrimitive()) {
                    try {
                        field.setAccessible(true);
                    } catch (SecurityException e) {
                        LOG.error("Security settings prevent Ehcache from accessing the subgraph beneath '{}'" +
                                  " - cache sizes may be underestimated as a result", field, e);
                        continue;
                    }
                    fields.add(field);
                }
            }
        }
        return fields;
    }

    private boolean byPassIfFlyweight(Object obj) {
        if(bypassFlyweight) {
            FlyweightType type = FlyweightType.getFlyweightType(obj.getClass());
            return type != null && type.isShared(obj);
        }
        return false;
    }

}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof.util;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A poor man's implementation of a WeakIdentityConcurrentMap to hold the CacheManager associated ExecutorServices
 *
 * @param <K> The key type
 * @param <V> The value type
 * @author Alex Snaps
 */
public final class WeakIdentityConcurrentMap<K, V> {

    private final ConcurrentMap<WeakReference<K>, V> map = new ConcurrentHashMap<>();
    private final ReferenceQueue<K> queue = new ReferenceQueue<>();

    private final CleanUpTask<V> cleanUpTask;

    /**
     * Constructor
     */
    public WeakIdentityConcurrentMap() {
        this(null);
    }

    /**
     * Constructor
     *
     * @param cleanUpTask task cleaning up references
     */
    public WeakIdentityConcurrentMap(final CleanUpTask<V> cleanUpTask) {
        this.cleanUpTask = cleanUpTask;
    }

    /**
     * Puts into the underlying
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>,
     *         if the implementation supports <tt>null</tt> values.)
     */
    public V put(K key, V value) {
        cleanUp();
        return map.put(new IdentityWeakReference<>(key, queue), value);
    }

    /**
     * Remove from the underlying
     *
     * @param key key whose mapping is to be removed from the map
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     */
    public V remove(K key) {
        cleanUp();
        return map.remove(new IdentityWeakReference<>(key, queue));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        cleanUp();
        return map.toString();
    }

    /**
     * Puts into the underlying
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with the specified key, or
     *         {@code null} if there was no mapping for the key.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with the key,
     *         if the implementation supports null values.)
     */
    public V putIfAbsent(K key, V value) {
        cleanUp();
        return map.putIfAbsent(new IdentityWeakReference<>(key, queue), value);
    }

    /**
     * @param key the key whose associated value is to be returned
     * @return the value to which the specified key is mapped, or
     *         {@code null} if this map contains no mapping for the key
     */
    public V get(K key) {
        cleanUp();
        return map.get(new IdentityWeakReference<>(key));
    }

    /**
     *
     */
    public void cleanUp() {

        Reference<? extends K> reference;
        while ((reference = queue.poll()) != null) {
            final V value = map.remove(reference);
            if (cleanUpTask != null && value != null) {
                cleanUpTask.cleanUp(value);
            }
        }
    }

    /**
     * @return a set view of the keys contained in this map
     */
    public Set<K> keySet() {
        cleanUp();
        K k;
        final HashSet<K> ks = new HashSet<>();
        for (WeakReference<K> weakReference : map.keySet()) {
            k = weakReference.get();
            if (k != null) {
                ks.add(k);
            }
        }
        return ks;
    }

    public boolean containsKey(final K key) {
        cleanUp();
        return map.containsKey(new IdentityWeakReference<>(key));
    }

    /**
     * @param <T>
     */
    private static final class IdentityWeakReference<T> extends WeakReference<T> {

        private final int hashCode;

        /**
         * @param reference the referenced object
         */
        IdentityWeakReference(T reference) {
            this(reference, null);
        }

        /**
         * @param reference the references object
         * @param referenceQueue the reference queue where references are kept
         */
        IdentityWeakReference(T reference, ReferenceQueue<T> referenceQueue) {
            super(reference, referenceQueue);
            this.hashCode = (reference == null) ? 0 : System.identityHashCode(reference);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return String.valueOf(get());
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof IdentityWeakReference<?>)) {
                return false;
            } else {
                IdentityWeakReference<?> wr = (IdentityWeakReference<?>)o;
                Object got = get();
                return (got != null && got == wr.get());
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int hashCode() {
            return hashCode;
        }
    }

    /**
     * @param <T>
     */
    public interface CleanUpTask<T> {

        /**
         * @param object object to cleanup
         */
        void cleanUp(T object);
    }
}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.List;

/**
 * This will try to load the agent using the Attach API of JDK6.
 * If you are on an older JDK (v5) you can still use the agent by adding the -javaagent:[pathTojar] to your VM
 * startup script
 *
 * @author Alex Snaps
 */
final class AgentLoader {

    private static final Logger LOGGER = LoggerFactory.getLogger(AgentLoader.class);

    private static final String SIZEOF_AGENT_CLASSNAME = "org.ehcache.sizeof.impl.SizeOfAgent";
    private static final String VIRTUAL_MACHINE_CLASSNAME = "com.sun.tools.attach.VirtualMachine";
    private static final Method VIRTUAL_MACHINE_ATTACH;
    private static final Method VIRTUAL_MACHINE_DETACH;
    private static final Method VIRTUAL_MACHINE_LOAD_AGENT;

    private static volatile Instrumentation instrumentation;

    static final String INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME = "org.ehcache.sizeof.agent.instrumentation";

    static {
        Method attach = null;
        Method detach = null;
        Method loadAgent = null;
        try {
            Class<?> virtualMachineClass = getVirtualMachineClass();
            attach = virtualMachineClass.getMethod("attach", String.class);
            detach = virtualMachineClass.getMethod("detach");
            loadAgent = virtualMachineClass.getMethod("loadAgent", String.class);
        } catch (Throwable e) {
            LOGGER.info("Unavailable or unrecognised attach API : {}", e.toString());
        }
        VIRTUAL_MACHINE_ATTACH = attach;
        VIRTUAL_MACHINE_DETACH = detach;
        VIRTUAL_MACHINE_LOAD_AGENT = loadAgent;
    }

    private static Class<?> getVirtualMachineClass() throws ClassNotFoundException {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<Class<?>>) () -> {
                try {
                    return ClassLoader.getSystemClassLoader().loadClass(VIRTUAL_MACHINE_CLASSNAME);
                } catch (ClassNotFoundException cnfe) {
                    for (File jar : getPossibleToolsJars()) {
                        try {
                            Class<?> vmClass = new URLClassLoader(new URL[] { jar.toURI().toURL() }).loadClass(VIRTUAL_MACHINE_CLASSNAME);
                            LOGGER.info("Located valid 'tools.jar' at '{}'", jar);
                            return vmClass;
                        } catch (Throwable t) {
                            LOGGER.info("Exception while loading tools.jar from '{}': {}", jar, t);
                        }
                    }
                    throw new ClassNotFoundException(VIRTUAL_MACHINE_CLASSNAME);
                }
            });
        } catch (PrivilegedActionException pae) {
            Throwable actual = pae.getCause();
            if (actual instanceof ClassNotFoundException) {
                throw (ClassNotFoundException)actual;
            }
            throw new AssertionError("Unexpected checked exception : " + actual);
        }
    }

    private static List<File> getPossibleToolsJars() {
        List<File> jars = new ArrayList<>();

        File javaHome = new File(System.getProperty("java.home"));
        File jreSourced = new File(javaHome, "lib/tools.jar");
        if (jreSourced.exists()) {
            jars.add(jreSourced);
        }
        if ("jre".equals(javaHome.getName())) {
            File jdkHome = new File(javaHome, "../");
            File jdkSourced = new File(jdkHome, "lib/tools.jar");
            if (jdkSourced.exists()) {
                jars.add(jdkSourced);
            }
        }
        return jars;
    }

    /**
     * Attempts to load the agent through the Attach API
     *
     * @return true if agent was loaded (which could have happened thought the -javaagent switch)
     */
    static boolean loadAgent() {
        synchronized (AgentLoader.class.getName().intern()) {
            if (!agentIsAvailable() && VIRTUAL_MACHINE_LOAD_AGENT != null) {
                try {
                    warnIfOSX();
                    String name = ManagementFactory.getRuntimeMXBean().getName();
                    Object vm = VIRTUAL_MACHINE_ATTACH.invoke(null, name.substring(0, name.indexOf('@')));
                    try {
                        File agent = getAgentFile();
                        LOGGER.info("Trying to load agent @ {}", agent);
                        if (agent != null) {
                            VIRTUAL_MACHINE_LOAD_AGENT.invoke(vm, agent.getAbsolutePath());
                        }
                    } finally {
                        VIRTUAL_MACHINE_DETACH.invoke(vm);
                    }
                } catch (InvocationTargetException ite) {
                    Throwable cause = ite.getCause();
                    LOGGER.info("Failed to attach to VM and load the agent: {}: {}", cause.getClass(), cause.getMessage());
                } catch (Throwable t) {
                    LOGGER.info("Failed to attach to VM and load the agent: {}: {}", t.getClass(), t.getMessage());
                }
            }
            final boolean b = agentIsAvailable();
            if (b) {
                LOGGER.info("Agent successfully loaded and available!");
            }

            return b;
        }
    }

    private static void warnIfOSX() {
        if (JvmInformation.isOSX() && System.getProperty("java.io.tmpdir") != null) {
            LOGGER.warn("Loading the SizeOfAgent will probably fail, as you are running on Apple OS X and have a value set for java.io.tmpdir\n" +
                        "They both result in a bug, not yet fixed by Apple, that won't let us attach to the VM and load the agent.\n" +
                        "Most probably, you'll also get a full thread-dump after this because of the failure... Nothing to worry about!\n" +
                        "You can bypass trying to load the Agent entirely by setting the System property '"
                        + AgentSizeOf.BYPASS_LOADING + "'  to true");
        }
    }

    private static File getAgentFile() throws IOException {
        URL agent = AgentLoader.class.getResource("sizeof-agent.jar");
        if (agent == null) {
            return null;
        } else if (agent.getProtocol().equals("file")) {
            return new File(agent.getFile());
        } else {
            File temp = File.createTempFile("ehcache-sizeof-agent", ".jar");
            try (FileOutputStream fout = new FileOutputStream(temp); InputStream in = agent.openStream()) {
                byte[] buffer = new byte[1024];
                while (true) {
                    int read = in.read(buffer);
                    if (read < 0) {
                        break;
                    } else {
                        fout.write(buffer, 0, read);
                    }
                }
            } finally {
                temp.deleteOnExit();
            }
            LOGGER.info("Extracted agent jar to temporary file {}", temp);
            return temp;
        }
    }

    /**
     * Checks whether the agent is available
     *
     * @return true if available
     */
    static boolean agentIsAvailable() {
        try {
            if (instrumentation == null) {
                instrumentation = (Instrumentation)System.getProperties().get(INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME);
            }
            if (instrumentation == null) {
                Class<?> sizeOfAgentClass = ClassLoader.getSystemClassLoader().loadClass(SIZEOF_AGENT_CLASSNAME);
                Method getInstrumentationMethod = sizeOfAgentClass.getMethod("getInstrumentation");
                instrumentation = (Instrumentation)getInstrumentationMethod.invoke(sizeOfAgentClass);
            }
            return instrumentation != null;
        } catch (SecurityException e) {
            LOGGER.warn("Couldn't access the system classloader because of the security policies applied by " +
                        "the security manager. You either want to loosen these, so ClassLoader.getSystemClassLoader() and " +
                        "reflection API calls are permitted or the sizing will be done using some other mechanism.\n" +
                        "Alternatively, set the system property org.ehcache.sizeof.agent.instrumentationSystemProperty to true " +
                        "to have the agent put the required instances in the System Properties for the loader to access.");
            return false;
        } catch (Throwable e) {
            return false;
        }
    }

    /**
     * Returns the size of this Java object as calculated by the loaded agent.
     *
     * @param obj object to be sized
     * @return size of the object in bytes
     */
    static long agentSizeOf(Object obj) {
        if (instrumentation == null) {
            throw new UnsupportedOperationException("Sizeof agent is not available");
        }
        return instrumentation.getObjectSize(obj);
    }
}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof.impl;

import sun.misc.Unsafe;
import org.ehcache.sizeof.SizeOf;
import org.ehcache.sizeof.filters.SizeOfFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION;

/**
 * {@link sun.misc.Unsafe#theUnsafe} based sizeOf measurement
 * All constructors will throw UnsupportedOperationException if theUnsafe isn't accessible on this platform
 *
 * @author Chris Dennis
 */
@SuppressWarnings("restriction")
public class UnsafeSizeOf extends SizeOf {

    private static final Logger LOGGER = LoggerFactory.getLogger(UnsafeSizeOf.class);

    private static final Unsafe UNSAFE;

    static {
        Unsafe unsafe;
        try {
            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            unsafe = (Unsafe)unsafeField.get(null);
        } catch (Throwable t) {
            unsafe = null;
        }
        UNSAFE = unsafe;
    }

    /**
     * Builds a new SizeOf that will not filter fields and will cache reflected fields
     *
     * @throws UnsupportedOperationException If Unsafe isn't accessible
     * @see #UnsafeSizeOf(org.ehcache.sizeof.filters.SizeOfFilter, boolean, boolean)
     */
    public UnsafeSizeOf() throws UnsupportedOperationException {
        this(new PassThroughFilter());
    }

    /**
     * Builds a new SizeOf that will filter fields according to the provided filter and will cache reflected fields
     *
     * @param filter The filter to apply
     * @throws UnsupportedOperationException If Unsafe isn't accessible
     * @see #UnsafeSizeOf(org.ehcache.sizeof.filters.SizeOfFilter, boolean, boolean)
     * @see org.ehcache.sizeof.filters.SizeOfFilter
     */
    public UnsafeSizeOf(SizeOfFilter filter) throws UnsupportedOperationException {
        this(filter, true, true);
    }

    /**
     * Builds a new SizeOf that will filter fields according to the provided filter
     *
     * @param filter            The filter to apply
     * @param caching           whether to cache reflected fields
     * @param bypassFlyweight   whether "Flyweight Objects" are to be ignored
     * @throws UnsupportedOperationException If Unsafe isn't accessible
     * @see SizeOfFilter
     */
    public UnsafeSizeOf(SizeOfFilter filter, boolean caching, boolean bypassFlyweight) throws UnsupportedOperationException {
        super(filter, caching, bypassFlyweight);
        if (UNSAFE == null) {
            throw new UnsupportedOperationException("sun.misc.Unsafe instance not accessible");
        }

        if (!CURRENT_JVM_INFORMATION.supportsUnsafeSizeOf()) {
            LOGGER.warn("UnsafeSizeOf is not always accurate on the JVM (" + CURRENT_JVM_INFORMATION.getJvmDescription() +
                        ").  Please consider enabling AgentSizeOf.");
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long sizeOf(Object obj) {
        if (obj.getClass().isArray()) {
            Class<?> klazz = obj.getClass();
            int base = UNSAFE.arrayBaseOffset(klazz);
            int scale = UNSAFE.arrayIndexScale(klazz);
            long size = base + (scale * Array.getLength(obj));
            size += CURRENT_JVM_INFORMATION.getFieldOffsetAdjustment();
            if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) {
                size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment());
            }
            return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), size);
        } else {
            for (Class<?> klazz = obj.getClass(); klazz != null; klazz = klazz.getSuperclass()) {
                long lastFieldOffset = -1;
                for (Field f : klazz.getDeclaredFields()) {
                    if (!Modifier.isStatic(f.getModifiers())) {
                        lastFieldOffset = Math.max(lastFieldOffset, UNSAFE.objectFieldOffset(f));
                    }
                }
                if (lastFieldOffset > 0) {
                    lastFieldOffset += CURRENT_JVM_INFORMATION.getFieldOffsetAdjustment();
                    lastFieldOffset += 1;
                    if ((lastFieldOffset % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) {
                        lastFieldOffset += CURRENT_JVM_INFORMATION.getObjectAlignment() -
                                           (lastFieldOffset % CURRENT_JVM_INFORMATION.getObjectAlignment());
                    }
                    return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), lastFieldOffset);
                }
            }

            long size = CURRENT_JVM_INFORMATION.getObjectHeaderSize();
            if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) {
                size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment());
            }
            return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), size);
        }
    }

}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof.filters;

import java.lang.reflect.Field;
import java.util.Collection;

/**
 * Filter combining multiple filters
 *
 * @author Chris Dennis
 */
public class CombinationSizeOfFilter implements SizeOfFilter {

    private final SizeOfFilter[] filters;

    /**
     * Constructs a filter combining multiple ones
     *
     * @param filters the filters to combine
     */
    public CombinationSizeOfFilter(SizeOfFilter... filters) {
        this.filters = filters;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<Field> filterFields(Class<?> klazz, Collection<Field> fields) {
        Collection<Field> current = fields;
        for (SizeOfFilter filter : filters) {
            current = filter.filterFields(klazz, current);
        }
        return current;
    }

    /**
     * {@inheritDoc}
     */
    public boolean filterClass(Class<?> klazz) {
        for (SizeOfFilter filter : filters) {
            if (!filter.filterClass(klazz)) {
                return false;
            }
        }
        return true;
    }
}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof.impl;

import org.ehcache.sizeof.SizeOf;
import org.ehcache.sizeof.filters.SizeOfFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;

import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION;

/**
 * SizeOf that uses reflection to measure on heap size of object graphs
 * Inspired by Dr. Heinz Kabutz's Java Specialist Newsletter Issue #78
 *
 * @author Alex Snaps
 * @author Chris Dennis
 *
 * @link http://www.javaspecialists.eu/archive/Issue078.html
 */
public class ReflectionSizeOf extends SizeOf {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionSizeOf.class);

    /**
     * Builds a new SizeOf that will not filter fields and will cache reflected fields
     *
     * @see #ReflectionSizeOf(SizeOfFilter, boolean, boolean)
     */
    public ReflectionSizeOf() {
        this(new PassThroughFilter());
    }

    /**
     * Builds a new SizeOf that will filter fields and will cache reflected fields
     *
     * @param fieldFilter The filter to apply
     * @see #ReflectionSizeOf(SizeOfFilter, boolean, boolean)
     * @see SizeOfFilter
     */
    public ReflectionSizeOf(SizeOfFilter fieldFilter) {
        this(fieldFilter, true, true);
    }

    /**
     * Builds a new SizeOf that will filter fields
     *
     * @param fieldFilter       The filter to apply
     * @param caching           Whether to cache reflected fields
     * @param bypassFlyweight   whether "Flyweight Objects" are to be ignored
     * @see SizeOfFilter
     */
    public ReflectionSizeOf(SizeOfFilter fieldFilter, boolean caching, boolean bypassFlyweight) {
        super(fieldFilter, caching, bypassFlyweight);

        if (!CURRENT_JVM_INFORMATION.supportsReflectionSizeOf()) {
            LOGGER.warn("ReflectionSizeOf is not always accurate on the JVM (" + CURRENT_JVM_INFORMATION.getJvmDescription() +
                        ").  Please consider enabling AgentSizeOf.");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long sizeOf(Object obj) {
        if (obj == null) {
            return 0;
        }

        Class<?> aClass = obj.getClass();
        if (aClass.isArray()) {
            return guessArraySize(obj);
        } else {
            long size = CURRENT_JVM_INFORMATION.getObjectHeaderSize();

            Deque<Class<?>> classStack = new ArrayDeque<>();
            for (Class<?> klazz = aClass; klazz != null; klazz = klazz.getSuperclass()) {
                classStack.push(klazz);
            }

            while (!classStack.isEmpty()) {
                Class<?> klazz = classStack.pop();

                //assuming default class layout
                int oops = 0;
                int doubles = 0;
                int words = 0;
                int shorts = 0;
                int bytes = 0;
                for (Field f : klazz.getDeclaredFields()) {
                    if (Modifier.isStatic(f.getModifiers())) {
                        continue;
                    }
                    if (f.getType().isPrimitive()) {
                        switch (PrimitiveType.forType(f.getType())) {
                            case BOOLEAN:
                            case BYTE:
                                bytes++;
                                break;
                            case SHORT:
                            case CHAR:
                                shorts++;
                                break;
                            case INT:
                            case FLOAT:
                                words++;
                                break;
                            case DOUBLE:
                            case LONG:
                                doubles++;
                                break;
                            default:
                                throw new AssertionError();
                        }
                    } else {
                        oops++;
                    }
                }
                if (doubles > 0 && (size % PrimitiveType.LONG.getSize()) != 0) {
                    long length = PrimitiveType.LONG.getSize() - (size % PrimitiveType.LONG.getSize());
                    size += PrimitiveType.LONG.getSize() - (size % PrimitiveType.LONG.getSize());

                    while (length >= PrimitiveType.INT.getSize() && words > 0) {
                        length -= PrimitiveType.INT.getSize();
                        words--;
                    }
                    while (length >= PrimitiveType.SHORT.getSize() && shorts > 0) {
                        length -= PrimitiveType.SHORT.getSize();
                        shorts--;
                    }
                    while (length >= PrimitiveType.BYTE.getSize() && bytes > 0) {
                        length -= PrimitiveType.BYTE.getSize();
                        bytes--;
                    }
                    while (length >= PrimitiveType.getReferenceSize() && oops > 0) {
                        length -= PrimitiveType.getReferenceSize();
                        oops--;
                    }
                }
                size += PrimitiveType.DOUBLE.getSize() * doubles;
                size += PrimitiveType.INT.getSize() * words;
                size += PrimitiveType.SHORT.getSize() * shorts;
                size += PrimitiveType.BYTE.getSize() * bytes;

                if (oops > 0) {
                    if ((size % PrimitiveType.getReferenceSize()) != 0) {
                        size += PrimitiveType.getReferenceSize() - (size % PrimitiveType.getReferenceSize());
                    }
                    size += oops * PrimitiveType.getReferenceSize();
                }

                if ((doubles + words + shorts + bytes + oops) > 0 && (size % PrimitiveType.getReferenceSize()) != 0) {
                    size += PrimitiveType.getReferenceSize() - (size % PrimitiveType.getReferenceSize());
                }
            }
            if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) {
                size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment());
            }
            return Math.max(size, CURRENT_JVM_INFORMATION.getMinimumObjectSize());
        }
    }

    private long guessArraySize(Object obj) {
        long size = PrimitiveType.getArraySize();
        int length = Array.getLength(obj);
        if (length != 0) {
            Class<?> arrayElementClazz = obj.getClass().getComponentType();
            if (arrayElementClazz.isPrimitive()) {
                size += length * PrimitiveType.forType(arrayElementClazz).getSize();
            } else {
                size += length * PrimitiveType.getReferenceSize();
            }
        }
        if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) {
            size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment());
        }
        return Math.max(size, CURRENT_JVM_INFORMATION.getMinimumObjectSize());
    }
}


/**
 * Copyright Terracotta, Inc.
 *
 * 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.ehcache.sizeof.filters;

import org.ehcache.sizeof.annotations.AnnotationProxyFactory;
import org.ehcache.sizeof.annotations.IgnoreSizeOf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public final class AnnotationSizeOfFilter implements SizeOfFilter {

    private static final String IGNORE_SIZE_OF_VM_ARGUMENT = AnnotationSizeOfFilter.class.getName() + ".pattern";

    private static final Logger LOG = LoggerFactory.getLogger(AnnotationSizeOfFilter.class.getName());

    //default is *cache.*IgnoreSizeOf
    private static final String IGNORE_SIZE_OF_DEFAULT_REGEXP = "^.*cache\\..*IgnoreSizeOf$";
    private static final Pattern IGNORE_SIZE_OF_PATTERN;

    static {
        String ignoreSizeOfRegexpVMArg = System.getProperty(IGNORE_SIZE_OF_VM_ARGUMENT);
        String ignoreSizeOfRegexp = ignoreSizeOfRegexpVMArg != null ? ignoreSizeOfRegexpVMArg : IGNORE_SIZE_OF_DEFAULT_REGEXP;
        Pattern localPattern;
        try {
            localPattern = Pattern.compile(ignoreSizeOfRegexp);
            LOG.info("Using regular expression provided through VM argument "
                     + IGNORE_SIZE_OF_VM_ARGUMENT
                     + " for IgnoreSizeOf annotation : "
                     + ignoreSizeOfRegexp);
        } catch (PatternSyntaxException e) {
            throw new IllegalArgumentException("Invalid regular expression provided through VM argument "
                                               + IGNORE_SIZE_OF_VM_ARGUMENT
                                               + " : \n"
                                               + e.getMessage()
                                               + "\n using default regular expression for IgnoreSizeOf annotation : "
                                               + IGNORE_SIZE_OF_DEFAULT_REGEXP);
        }
        IGNORE_SIZE_OF_PATTERN = localPattern;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<Field> filterFields(Class<?> klazz, Collection<Field> fields) {
        for (Iterator<Field> it = fields.iterator(); it.hasNext(); ) {

            Field field = it.next();
            IgnoreSizeOf annotationOnField = getAnnotationOn(field, IgnoreSizeOf.class, IGNORE_SIZE_OF_PATTERN);
            if (annotationOnField != null) {
                it.remove();
            }
        }
        return fields;
    }

    /**
     * {@inheritDoc}
     */
    public boolean filterClass(Class<?> klazz) {
        boolean classAnnotated = isAnnotationPresentOrInherited(klazz);
        Package pack = klazz.getPackage();
        IgnoreSizeOf annotationOnPackage = pack == null ? null : getAnnotationOn(pack, IgnoreSizeOf.class, IGNORE_SIZE_OF_PATTERN);
        boolean packageAnnotated = annotationOnPackage != null;
        return !classAnnotated && !packageAnnotated;
    }

    private boolean isAnnotationPresentOrInherited(final Class<?> instanceKlazz) {
        Class<?> klazz = instanceKlazz;
        while (klazz != null) {
            IgnoreSizeOf annotationOnClass = getAnnotationOn(klazz, IgnoreSizeOf.class, IGNORE_SIZE_OF_PATTERN);
            if (annotationOnClass != null && (klazz == instanceKlazz || annotationOnClass.inherited())) {
                return true;
            }
            klazz = klazz.getSuperclass();
        }
        return false;
    }

    private boolean validateCustomAnnotationPattern(String canonicalName, Pattern matchingAnnotationPattern) {
        Matcher matcher = matchingAnnotationPattern.matcher(canonicalName);

        boolean found = matcher.matches();
        if (found) {
            LOG.debug(canonicalName + " matched IgnoreSizeOf annotation pattern " + IGNORE_SIZE_OF_PATTERN);
        }
        return found;
    }

    //EHC-938 : looking for all types of IgnoreSizeOf annotations
    private <T extends Annotation> T getAnnotationOn(AnnotatedElement element, Class<T> referenceAnnotation, Pattern matchingAnnotationPattern) {
        T matchingAnnotation = null;
        Annotation[] annotations = element.getAnnotations();
        for (Annotation annotation : annotations) {
            if (validateCustomAnnotationPattern(annotation.annotationType().getName(), matchingAnnotationPattern)) {
                if (matchingAnnotation != null) {
                    throw new IllegalStateException("You are not allowed to use more than one @" + referenceAnnotation.getName()
                                                    + " annotations for the same element : "
                                                    + element.toString());
                }
                matchingAnnotation = AnnotationProxyFactory.getAnnotationProxy(annotation, referenceAnnotation);
            }
        }
        return matchingAnnotation;
    }
}


package com.wizzardo.epoll;

import com.wizzardo.tools.misc.Unchecked;
import com.wizzardo.tools.reflection.FieldReflection;
import com.wizzardo.tools.reflection.FieldReflectionFactory;
import com.wizzardo.tools.reflection.UnsafeTools;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.regex.Pattern;

import static com.wizzardo.epoll.Utils.readInt;
import static com.wizzardo.epoll.Utils.readShort;

/**
 * @author: wizzardo
 * Date: 11/5/13
 */
public class EpollCore<T extends Connection> extends Thread implements ByteBufferProvider {
    //  gcc -m32 -shared -fpic -o ../../../../../libepoll-core_x32.so -I /home/moxa/soft/jdk1.6.0_45/include/ -I /home/moxa/soft/jdk1.6.0_45/include/linux/ EpollCore.c
    //  gcc      -shared -fpic -o ../../../../../libepoll-core_x64.so -I /home/moxa/soft/jdk1.6.0_45/include/ -I /home/moxa/soft/jdk1.6.0_45/include/linux/ EpollCore.c
    //  javah -jni com.wizzardo.epoll.EpollCore

    public static final boolean SUPPORTED;

    ByteBuffer events;
    volatile long scope;
    volatile long sslContextPointer;
    protected volatile boolean running = true;
    protected volatile boolean started = false;
    protected final ByteBufferWrapper buffer = new ByteBufferWrapper(ByteBuffer.allocateDirect(16 * 1024));
    protected static final Pattern IP_PATTERN = Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
    protected int ioThreadsCount = Runtime.getRuntime().availableProcessors();
    protected SslConfig sslConfig;
    protected long ttl = 30000;

    private IOThread[] ioThreads;

    static {
        boolean supported = false;
        try {
            Utils.loadLib("libepoll-core");
            supported = true;
            System.out.println("epoll-core lib loaded");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("epoll is not supported");
        }
        SUPPORTED = supported;
    }

    public EpollCore() {
        this(100);
    }

    public EpollCore(int maxEvents) {
        initEpoll(maxEvents);
    }

    protected void initEpoll(int maxEvents) {
        events = ByteBuffer.allocateDirect(Math.max((maxEvents + 500) * 11, 16 * 1024));
        scope = init(maxEvents, events);
    }

    public void setIoThreadsCount(int ioThreadsCount) {
        this.ioThreadsCount = ioThreadsCount;
    }

    public boolean isStarted() {
        return started;
    }

//    protected AtomicInteger eventCounter = new AtomicInteger(0);

    @Override
    public void run() {
        started = true;
        System.out.println("io threads count: " + ioThreadsCount);
        ioThreads = new IOThread[ioThreadsCount];
        for (int i = 0; i < ioThreadsCount; i++) {
            ioThreads[i] = createIOThread(i, ioThreadsCount);
            ioThreads[i].setTTL(ttl);
            ioThreads[i].loadCertificates(sslConfig);
            ioThreads[i].start();
        }

        ByteBuffer eventsBuffer = this.events;
        byte[] events = new byte[eventsBuffer.capacity()];
        byte[] newConnections = new byte[eventsBuffer.capacity()];

        if (ioThreadsCount == 0) {
            IOThread<? extends T> ioThread = createIOThread(1, 1);
            ioThreadsCount = 1;
            ioThread.scope = scope;
            ioThreads = new IOThread[]{ioThread};
            ioThreads[0].setTTL(ttl);
            ioThreads[0].loadCertificates(sslConfig);

            long prev = System.nanoTime();
            while (running) {
                try {
                    eventsBuffer.position(0);
                    long now = System.nanoTime() * 1000;
                    long nowMinusSecond = now - 1_000_000_000_000L; // 1 sec
                    int r = waitForEvents(500);
                    eventsBuffer.limit(r);
                    eventsBuffer.get(events, 0, r);
                    int i = 0;
                    while (i < r) {
                        int event = events[i];
                        if (event == 0) {
                            acceptConnections(newConnections);
                        } else {
                            int fd = readInt(events, i + 1);
                            ioThread.handleEvent(fd, event, now, nowMinusSecond);
                        }
                        i += 5;
                    }
                    if (nowMinusSecond > prev) {
                        ioThread.handleTimeOuts(now);
                        prev = now;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return;
        }

        while (running) {
            try {
                eventsBuffer.position(0);
                int r = waitForEvents(500);
                eventsBuffer.limit(r);
                eventsBuffer.get(events, 0, r);
                int i = 0;
                while (i < r) {
                    int event = events[i];
                    i += 5;
                    if (event == 0)
                        acceptConnections(newConnections);
                    else
                        throw new IllegalStateException("this thread only for accepting new connections, event: " + event);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        for (int i = 0; i < ioThreads.length; i++) {
            ioThreads[i].close();
        }
    }

    public void setTTL(long milliseconds) {
        ttl = milliseconds;
    }

    public long getTTL() {
        return ttl;
    }

    public void close() {
        synchronized (this) {
            if (running) {
                running = false;
                stopListening(scope);
                try {
                    join();
                } catch (InterruptedException ignored) {
                }
            }
        }
    }

    private void acceptConnections(byte[] buffer) throws IOException {
        int k;
        do {
            events.position(0);
            k = acceptConnections(scope, events.capacity() - 10);
            events.limit(k);
            events.get(buffer, 0, k);
//          eventCounter.addAndGet(k / 10);
            for (int j = 0; j < k; j += 10) {
                putConnection(createConnection(readInt(buffer, j), readInt(buffer, j + 4), readShort(buffer, j + 8)));
            }
        } while (k > 0);
    }

    private void putConnection(T connection) throws IOException {
        ioThreads[connection.fd % ioThreadsCount].putConnection(connection);
    }

    public T connect(String host, int port) throws IOException {
        return connect(host, port, this::createConnection);
    }

    public T connect(String host, int port, ConnectionFactory<T> factory) throws IOException {
        boolean resolve = !IP_PATTERN.matcher(host).matches();
        if (resolve) {
            InetAddress address = InetAddress.getByName(host);
            host = address.getHostAddress();
        }
        T connection;
        if (Thread.currentThread() instanceof IOThread) {
            IOThread ioThread = (IOThread) Thread.currentThread();
            int fd = ioThread.connect(scope, host, port, ioThread.divider, ioThread.number);
            connection = factory.create(fd, 0, port);
            ioThread.putConnection(connection);
        } else {
            connection = factory.create(connect(scope, host, port, 1, 0), 0, port);
            putConnection(connection);
        }
        connection.setIpString(host);
        return connection;
    }

    protected boolean bind(String host, int port) {
        try {
            listen(scope, host, String.valueOf(port));
        } catch (Exception e) {
            throw Unchecked.rethrow(e);
        }
        return true;
    }

    protected int waitForEvents(int timeout) {
        return waitForEvents(scope, timeout);
    }

    protected int waitForEvents() {
        return waitForEvents(scope, -1);
    }

    void mod(Connection connection, int mode) {
        if (connection.isAlive() && connection.getMode() != mode) {
            synchronized (connection) {
                if (connection.isAlive() && connection.getMode() != mode)
                    if (!mod(scope, connection.fd, mode))
                        connection.close();
                    else
                        connection.setMode(mode);
            }
        }
    }

    @Override
    public ByteBufferWrapper getBuffer() {
        return buffer;
    }

    protected T createConnection(int fd, int ip, int port) {
        return (T) new Connection(fd, ip, port);
    }

    protected IOThread<? extends T> createIOThread(int number, int divider) {
        return new IOThread<T>(number, divider);
    }

    long createSSL(int fd) {
        return EpollSSL.createSSL(sslContextPointer, fd);
    }

    public void loadCertificates(String certFile, String keyFile) {
        loadCertificates(new SslConfig(certFile, keyFile));
    }

    public void loadCertificates(SslConfig sslConfig) {
        this.sslConfig = sslConfig;
    }

    protected boolean isSecured() {
        return sslConfig != null;
    }

    native void close(int fd);

    native boolean attach(long scope, int fd);

    private native long init(int maxEvents, ByteBuffer events);

    private native void listen(long scope, String host, String port) throws IOException;

    private native boolean stopListening(long scope);

    private native int waitForEvents(long scope, int timeout);

    private native int acceptConnections(long scope, int limit);

    native int connect(long scope, String host, int port, int divider, int number);

    private native boolean mod(long scope, int fd, int mode);

    native int read(int fd, long bbPointer, int off, int len) throws IOException;

    native int write(int fd, long bbPointer, int off, int len) throws IOException;

    private native static long getAddress(ByteBuffer buffer);

    private static final FieldReflection byteBufferAddressReflection = getByteBufferAddressReflection();

    private static FieldReflection getByteBufferAddressReflection() {
        try {
            return new FieldReflectionFactory().create(Buffer.class, "address", true);
        } catch (NoSuchFieldException e) {
            throw Unchecked.rethrow(e);
        }
    }

    static long address(ByteBuffer buffer) {
        return SUPPORTED ? getAddress(buffer) : byteBufferAddressReflection.getLong(buffer);
    }

    public static void arraycopy(ByteBuffer src, int srcPos, ByteBuffer dest, int destPos, int length) {
        if (length < 0)
            throw new IndexOutOfBoundsException("length must be >= 0. (length = " + length + ")");
        if (srcPos < 0)
            throw new IndexOutOfBoundsException("srcPos must be >= 0. (srcPos = " + srcPos + ")");
        if (destPos < 0)
            throw new IndexOutOfBoundsException("destPos must be >= 0. (destPos = " + destPos + ")");
        if (srcPos + length > src.capacity())
            throw new IndexOutOfBoundsException("srcPos + length must be <= src.capacity(). (srcPos = " + srcPos + ", length = " + length + ", capacity = " + src.capacity() + ")");
        if (destPos + length > dest.capacity())
            throw new IndexOutOfBoundsException("destPos + length must be <= dest.capacity(). (destPos = " + destPos + ", length = " + length + ", capacity = " + dest.capacity() + ")");

        if (SUPPORTED) {
            copy(src, srcPos, dest, destPos, length);
        } else {
            UnsafeTools.getUnsafe().copyMemory(address(src) + srcPos, address(dest) + destPos, length);
        }
    }

    private native static void copy(ByteBuffer src, int srcPos, ByteBuffer dest, int destPos, int length);

    public static void copy(ByteBufferWrapper src, int srcPos, ByteBufferWrapper dest, int destPos, int length) {
        copyMemory(src.address + srcPos, dest.address + destPos, length);
    }

    private static native void copyMemory(long srcPos, long dest, int length);

    public static void copyInto(ByteBufferWrapper dest, int destPos,
                                ByteBufferWrapper src1,
                                ByteBufferWrapper src2,
                                ByteBufferWrapper src3,
                                ByteBufferWrapper src4,
                                ByteBufferWrapper src5
    ) {
        copyInto5(dest.address + destPos,
                src1.address, src1.capacity(),
                src2.address, src2.capacity(),
                src3.address, src3.capacity(),
                src4.address, src4.capacity(),
                src5.address, src5.capacity()
        );
    }

    public static void copyInto(ByteBufferWrapper dest, int destPos,
                                ByteBufferWrapper src1,
                                ByteBufferWrapper src2,
                                ByteBufferWrapper src3,
                                ByteBufferWrapper src4) {
        copyInto4(dest.address + destPos,
                src1.address, src1.capacity(),
                src2.address, src2.capacity(),
                src3.address, src3.capacity(),
                src4.address, src4.capacity());
    }

    public static void copyInto(ByteBufferWrapper dest, int destPos,
                                ByteBufferWrapper src1,
                                ByteBufferWrapper src2,
                                ByteBufferWrapper src3) {
        copyInto3(dest.address + destPos,
                src1.address, src1.capacity(),
                src2.address, src2.capacity(),
                src3.address, src3.capacity());
    }

    public static void copyInto(ByteBufferWrapper dest, int destPos,
                                ByteBufferWrapper src1,
                                ByteBufferWrapper src2) {
        copyInto2(dest.address + destPos,
                src1.address, src1.capacity(),
                src2.address, src2.capacity());
    }

    private static native void copyInto5(long dest, long s1, int l1, long s2, int l2, long s3, int l3, long s4, int l4, long s5, int l5);

    private static native void copyInto4(long dest, long s1, int l1, long s2, int l2, long s3, int l3, long s4, int l4);

    private static native void copyInto3(long dest, long s1, int l1, long s2, int l2, long s3, int l3);

    private static native void copyInto2(long dest, long s1, int l1, long s2, int l2);
}


package com.wizzardo.epoll;

import com.wizzardo.epoll.readable.ReadableByteArray;
import com.wizzardo.epoll.readable.ReadableData;
import com.wizzardo.tools.io.IOTools;

import java.io.*;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author: wizzardo
 * Date: 1/6/14
 */
public class Connection implements Cloneable, Closeable {
    protected static final int EPOLLIN = 0x001;
    protected static final int EPOLLOUT = 0x004;

    protected int fd;
    protected int ip, port;
    protected volatile Deque<ReadableData> sending;
    protected volatile IOThread epoll;
    protected volatile long ssl;
    protected volatile boolean sslAccepted;
    protected final AtomicReference<ByteBufferProvider> writer = new AtomicReference<>();
    protected EpollInputStream inputStream;
    protected EpollOutputStream outputStream;
    protected ReadListener<Connection> readListener;
    protected WriteListener<Connection> writeListener;
    protected ConnectListener<Connection> connectListener;
    protected DisconnectListener<Connection> disconnectListener;
    protected ErrorListener<Connection> errorListener;
    private volatile int mode = 1;
    private volatile boolean alive = true;
    volatile boolean readyToRead = true;
    private String ipString;
    private long lastEvent;

    public Connection(int fd, int ip, int port) {
        this.fd = fd;
        this.ip = ip;
        this.port = port;
    }

    public String getIp() {
        if (ipString == null)
            ipString = getIp(ip);
        return ipString;
    }

    void setIpString(String ip) {
        ipString = ip;
    }

    private String getIp(int ip) {
        StringBuilder sb = new StringBuilder();
        sb.append((ip >> 24) + (ip < 0 ? 256 : 0)).append(".");
        sb.append((ip & 16777215) >> 16).append(".");
        sb.append((ip & 65535) >> 8).append(".");
        sb.append(ip & 255);
        return sb.toString();
    }

    public int getPort() {
        return port;
    }

    public boolean isReadyToRead() {
        return readyToRead;
    }

    long setLastEvent(long lastEvent) {
        long last = this.lastEvent;
        this.lastEvent = lastEvent;
        return last;
    }

    long getLastEvent() {
        return lastEvent;
    }

    public boolean isAlive() {
        return alive;
    }

    protected synchronized void setIsAlive(boolean isAlive) {
        alive = isAlive;
    }

    public boolean write(String s, ByteBufferProvider bufferProvider) {
        try {
            return write(s.getBytes("utf-8"), bufferProvider);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public boolean write(byte[] bytes, ByteBufferProvider bufferProvider) {
        return write(bytes, 0, bytes.length, bufferProvider);
    }

    public boolean write(byte[] bytes, int offset, int length, ByteBufferProvider bufferProvider) {
        return write(new ReadableByteArray(bytes, offset, length), bufferProvider);
    }

    public boolean write(ReadableData readable, ByteBufferProvider bufferProvider) {
        if (sending == null) {
            if (writer.compareAndSet(null, bufferProvider)) {
                try {
                    while (!readable.isComplete() && actualWrite(readable, bufferProvider)) {
                    }

                    if (!readable.isComplete()) {
                        safeCreateQueueIfNotExists();
                        sending.addFirst(readable);
                        return false;
                    }

                    readable.close();
                    readable.onComplete();
                    onWriteData(readable, false);
                } catch (Exception e) {
                    e.printStackTrace();
                    close();
                    return false;
                } finally {
                    writer.set(null);
                }
            } else {
                safeCreateQueueIfNotExists();
                sending.add(readable);
            }
            return write(bufferProvider);
        } else {
            sending.add(readable);
            return write(bufferProvider);
        }
    }

    protected void safeCreateQueueIfNotExists() {
        if (sending == null)
            synchronized (this) {
                if (sending == null)
                    sending = createSendingQueue();
            }
    }

    protected Deque<ReadableData> createSendingQueue() {
        return new ConcurrentLinkedDeque<>();
    }

    /*
     * @return true if connection is ready to write data
     */
    public boolean write(ByteBufferProvider bufferProvider) {
        Queue<ReadableData> queue = this.sending;
        if (queue == null)
            return true;

        while (!queue.isEmpty() && writer.compareAndSet(null, bufferProvider)) {
            try {
                ReadableData readable;
                while ((readable = queue.peek()) != null) {
                    while (!readable.isComplete() && actualWrite(readable, bufferProvider)) {
                    }
                    if (!readable.isComplete())
                        return false;

                    queue.poll();
                    readable.close();
                    readable.onComplete();
                    onWriteData(readable, !queue.isEmpty());
                }
            } catch (Exception e) {
                e.printStackTrace();
                close();
                return false;
            } finally {
                writer.set(null);
            }
        }
        return true;
    }

    protected int write(ByteBufferWrapper wrapper, int off, int len) throws IOException {
        return epoll.write(this, wrapper.address, off, len);
    }

    /*
     * @return true if connection is ready to write data
     */
    protected boolean actualWrite(ReadableData readable, ByteBufferProvider bufferProvider) throws IOException {
        if (!isAlive())
            return false;

        ByteBufferWrapper bb = readable.getByteBuffer(bufferProvider);
        bb.clear();
        int r = readable.read(bb);
        if (r > 0) {
//            int written = epoll.write(this, bb.address, bb.offset(), r);
            int written = write(bb, bb.offset(), r);
            bb.clear();
//            System.out.println("write: " + written + " (" + readable.complete() + "/" + readable.length() + ")" + " to " + this);
            if (written != r) {
                readable.unread(r - written);
                return false;
            }
            return true;
        }
        return false;
    }

    public int read(byte[] b, int offset, int length, ByteBufferProvider bufferProvider) throws IOException {
        ByteBuffer bb = read(length, bufferProvider);
        int r = bb.limit();
        bb.get(b, offset, r);
        return r;
    }

    public ByteBuffer read(int length, ByteBufferProvider bufferProvider) throws IOException {
        ByteBufferWrapper bb = bufferProvider.getBuffer();
        bb.clear();
        int l = Math.min(length, bb.limit());
        int r = isAlive() ? epoll.read(this, bb.address, 0, l) : -1;
        if (r > 0)
            bb.position(r);
        bb.flip();
        readyToRead = r == l;
        return bb.buffer();
    }

    public void close() {
        if (sending != null)
            for (ReadableData data : sending)
                IOTools.close(data);

        if (ssl != 0)
            EpollSSL.closeSSL(ssl);

        epoll.close(this);
    }

    public boolean isSecured() {
        return ssl != 0;
    }

    protected void enableOnWriteEvent() {
        if ((mode & EPOLLIN) != 0)
            epoll.mod(this, EPOLLIN | EPOLLOUT);
        else
            epoll.mod(this, EPOLLOUT);
    }

    protected void disableOnWriteEvent() {
        if ((mode & EPOLLIN) != 0)
            epoll.mod(this, EPOLLIN);
        else
            epoll.mod(this, 0);
    }

    protected void enableOnReadEvent() {
        if ((mode & EPOLLOUT) != 0)
            epoll.mod(this, EPOLLIN | EPOLLOUT);
        else
            epoll.mod(this, EPOLLIN);
    }

    protected void disableOnReadEvent() {
        if ((mode & EPOLLOUT) != 0)
            epoll.mod(this, EPOLLOUT);
        else
            epoll.mod(this, 0);
    }

    public void onWriteData(ReadableData readable, boolean hasMore) throws IOException {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Connection that = (Connection) o;

        if (fd != that.fd) return false;
        if (ip != that.ip) return false;
        if (port != that.port) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = fd;
        result = 31 * result + ip;
        result = 31 * result + port;
        return result;
    }

    @Override
    public String toString() {
        return fd + " " + getIp() + ":" + port;
    }

    public int read(byte[] bytes, ByteBufferProvider bufferProvider) throws IOException {
        return read(bytes, 0, bytes.length, bufferProvider);
    }

    boolean isInvalid(long now) {
        return lastEvent <= now;
    }

    void setMode(int mode) {
        this.mode = mode;
    }

    int getMode() {
        return mode;
    }

    public void setIOThread(IOThread IOThread) {
        epoll = IOThread;
    }

    boolean prepareSSL() {
        if (ssl == 0)
            ssl = epoll.createSSL(fd);

        if (!sslAccepted) {
            synchronized (this) {
                if (!sslAccepted)
                    sslAccepted = EpollSSL.acceptSSL(ssl);
            }
        } else
            return true;

        return sslAccepted;
    }

    public void onRead(ByteBufferProvider bufferProvider) throws IOException {
        if (readListener != null)
            readListener.onRead(this, bufferProvider);
    }

    public void onWrite(ByteBufferProvider bufferProvider) throws IOException {
        if (!write(bufferProvider))
            return;

        if (writeListener != null)
            writeListener.onWrite(this, bufferProvider);
    }

    public void onConnect(ByteBufferProvider bufferProvider) throws IOException {
        if (connectListener != null)
            connectListener.onConnect(this, bufferProvider);
    }

    public void onDisconnect(ByteBufferProvider bufferProvider) throws IOException {
        if (disconnectListener != null)
            disconnectListener.onDisconnect(this, bufferProvider);
    }

    public void onError(ByteBufferProvider bufferProvider, Exception e) throws IOException {
        if (errorListener != null)
            errorListener.onError(this, e, bufferProvider);
    }

    public ReadListener<Connection> getReadListener() {
        return readListener;
    }

    public WriteListener<Connection> getWriteListener() {
        return writeListener;
    }

    protected EpollInputStream createInputStream(byte[] buffer, int currentOffset, int currentLimit, long contentLength) {
        return new EpollInputStream(this, buffer, currentOffset, currentLimit, contentLength);
    }

    protected EpollOutputStream createOutputStream() {
        return new EpollOutputStream(this);
    }

    public EpollInputStream getInputStream() {
        if (inputStream == null) {
            byte[] buffer = new byte[16 * 1024];
            inputStream = new EpollInputStream(this, buffer);
            readListener = (connection, bufferProvider) -> inputStream.wakeUp();
        }

        return inputStream;
    }

    public void flush() throws IOException {
        if (outputStream != null)
            outputStream.flush();
    }

    public EpollOutputStream getOutputStream() {
        if (outputStream == null) {
            outputStream = createOutputStream();
            writeListener = (connection, bufferProvider) -> outputStream.wakeUp();
        }

        return outputStream;
    }

    public void onRead(ReadListener<Connection> listener) {
        this.readListener = listener;
    }

    public void onWrite(WriteListener<Connection> listener) {
        this.writeListener = listener;
    }

    public void onConnect(ConnectListener<Connection> listener) {
        this.connectListener = listener;
    }

    public void onDisconnect(DisconnectListener<Connection> listener) {
        this.disconnectListener = listener;
    }

    public void onError(ErrorListener<Connection> listener) {
        this.errorListener = listener;
    }
}