package de.braintags.vertx.util.freezable;

import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

import com.fasterxml.jackson.annotation.JsonIgnore;

public interface Freezable<F extends Freezable<?>> {

  @JsonIgnore
  public boolean isFrozen();

  public void freeze();

  @JsonIgnore
  @SuppressWarnings({ "unchecked", "rawtypes" })
  public default F copy() {
    try {
      F copy = (F) instantiateProtected(this.getClass());
      ((Freezable) copy).copyFrom(this);
      return copy;
    } catch (InstantiationException e) {
      if (e.getCause() instanceof NoSuchMethodException) {
        throw new IllegalStateException(
            "No no args contructor found and deepCopy not overrided for class: " + this.getClass());
      }
      throw new RuntimeException(e);
    }
  }

  @JsonIgnore
  public void copyFrom(final F source);

  @JsonIgnore
  @SuppressWarnings("unchecked")
  public default F unfreeze() {
    synchronized (this) {
      if (isFrozen()) {
        return copy();
      } else {
        return (F) this;
      }
    }
  }

  public abstract static class Skeleton<F extends Freezable<?>> implements Freezable<F> {

    @JsonIgnore
    private boolean frozen;

    @Override
    @JsonIgnore
    public boolean isFrozen() {
      return frozen;
    }

    @Override
    public synchronized void freeze() {
      frozen = true;
    }

    protected final void checkFrozen() {
      if (isFrozen()) {
        throw new FrozenException("frozen");
      }
    }

    @JsonIgnore
    @Override
    public void copyFrom(final F source) {
      checkFrozen();
    }
  }

  public abstract static class Wrapper<F extends Freezable<?>> implements Freezable<F> {

    private final Freezable<?> wrapped;

    public Wrapper(final Freezable<?> wrapped) {
      this.wrapped = wrapped;
    }

    @Override
    @JsonIgnore
    public boolean isFrozen() {
      return wrapped.isFrozen();
    }

    @Override
    public synchronized void freeze() {
      wrapped.freeze();
    }

    protected final void checkFrozen() {
      if (isFrozen()) {
        throw new FrozenException("frozen");
      }
    }

    @JsonIgnore
    @Override
    public void copyFrom(final F source) {
      checkFrozen();
    }
  }

  public static <F> F instantiateProtected(final Class<F> cls) throws InstantiationException {
    F copy;
    try {
      copy = cls.newInstance();
    } catch (IllegalAccessException e) {
      try {
        copy = AccessController.doPrivileged((PrivilegedExceptionAction<F>) () -> {
          Constructor<F> constructor = cls.getDeclaredConstructor();
          if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
          }
          return constructor.newInstance();
        });
      } catch (PrivilegedActionException e1) {
        throw new RuntimeException(e1);
      }
    }
    return copy;
  }
}

@@ -1768,32 +1768,301 @@
 rozen();%0D%0A    %7D%0D
+%0A%0D%0A    // important for ProxyCache checks%0D%0A    @Override%0D%0A    public int hashCode() %7B%0D%0A      return super.hashCode();%0D%0A    %7D%0D%0A%0D%0A    // important for ProxyCache checks%0D%0A    @Override%0D%0A    public boolean equals(final Object obj) %7B%0D%0A      return super.equals(obj);%0D%0A    %7D%0D
 %0A  %7D%0D%0A%0D%0A  public
@@ -2704,32 +2704,690 @@
 ozen();%0D%0A    %7D%0D%0A
+%0D%0A    @Override%0D%0A    public int hashCode() %7B%0D%0A      final int prime = 31;%0D%0A      int result = 1;%0D%0A      result = prime * result + ((wrapped == null) ? 0 : wrapped.hashCode());%0D%0A      return result;%0D%0A    %7D%0D%0A%0D%0A    @Override%0D%0A    public boolean equals(final Object obj) %7B%0D%0A      if (this == obj)%0D%0A        return true;%0D%0A      if (obj == null)%0D%0A        return false;%0D%0A      if (getClass() != obj.getClass())%0D%0A        return false;%0D%0A      Wrapper other = (Wrapper) obj;%0D%0A      if (wrapped == null) %7B%0D%0A        if (other.wrapped != null)%0D%0A          return false;%0D%0A      %7D else if (!wrapped.equals(other.wrapped))%0D%0A        return false;%0D%0A      return true;%0D%0A    %7D%0D%0A%0D%0A
   %7D%0D%0A%0D%0A  public 

improve caches