/*
 * Copyright (c) 2012-2015 Spotify AB
 *
 * 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.spotify.netty4.handler.codec.zmtp;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;

import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
import static io.netty.util.CharsetUtil.UTF_8;

/**
 * Configuration for a ZMTP session and {@link ZMTPCodec}. Can be reused and shared across multiple
 * channel instances.
 */
public class ZMTPConfig {

  public static final ByteBuffer ANONYMOUS = ByteBuffer.allocate(0).asReadOnlyBuffer();

  private final ZMTPProtocol protocol;
  private final boolean interop;
  private final ZMTPSocketType socketType;
  private final ByteBuffer localIdentity;
  private final ZMTPEncoder.Factory encoder;
  private final ZMTPDecoder.Factory decoder;

  private ZMTPConfig(final Builder builder) {
    this.protocol = checkNotNull(builder.protocol, "protocol");
    this.interop = checkNotNull(builder.interop, "interop");
    this.socketType = checkNotNull(builder.socketType, "socketType");
    this.localIdentity = checkNotNull(builder.localIdentity, "localIdentity");
    this.encoder = checkNotNull(builder.encoder, "encoder");
    this.decoder = checkNotNull(builder.decoder, "decoder");
  }

  public ZMTPProtocol protocol() {
    return protocol;
  }

  public boolean interop() {
    return interop;
  }

  public ZMTPSocketType socketType() {
    return socketType;
  }

  public ByteBuffer localIdentity() {
    return localIdentity;
  }

  public ZMTPEncoder.Factory encoder() {
    return encoder;
  }

  public ZMTPDecoder.Factory decoder() {
    return decoder;
  }

  public Builder toBuilder() {
    return new Builder(this);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static class Builder {

    private ZMTPProtocol protocol = ZMTPProtocols.ZMTP20;
    private boolean interop = true;
    private ZMTPSocketType socketType;
    private ByteBuffer localIdentity = ANONYMOUS;
    private ZMTPEncoder.Factory encoder = ZMTPMessageEncoder.FACTORY;
    private ZMTPDecoder.Factory decoder = ZMTPMessageDecoder.FACTORY;

    private Builder() {
    }

    private Builder(final ZMTPConfig config) {
      this.protocol = config.protocol;
      this.interop = config.interop;
      this.socketType = config.socketType;
      this.localIdentity = config.localIdentity;
      this.encoder = config.encoder;
      this.decoder = config.decoder;

    }

    public Builder protocol(final ZMTPProtocol protocol) {
      this.protocol = protocol;
      return this;
    }

    public Builder interop(final boolean interop) {
      this.interop = interop;
      return this;
    }

    public Builder socketType(final ZMTPSocketType socketType) {
      this.socketType = socketType;
      return this;
    }

    public Builder localIdentity(final CharSequence localIdentity) {
      return localIdentity(UTF_8.encode(localIdentity.toString()));
    }

    public Builder localIdentity(final byte[] localIdentity) {
      return localIdentity(ByteBuffer.wrap(localIdentity));
    }

    public Builder localIdentity(final ByteBuffer localIdentity) {
      this.localIdentity = localIdentity;
      return this;
    }

    public Builder encoder(final ZMTPEncoder.Factory encoder) {
      this.encoder = encoder;
      return this;
    }

    public Builder encoder(final Class<? extends ZMTPEncoder> encoder) {
      return encoder(new ZMTPEncoderClassFactory(encoder));
    }

    public Builder decoder(final ZMTPDecoder.Factory decoder) {
      this.decoder = decoder;
      return this;
    }

    public Builder decoder(final Class<? extends ZMTPDecoder> decoder) {
      return decoder(new ZMTPDecoderClassFactory(decoder));
    }

    public ZMTPConfig build() {
      return new ZMTPConfig(this);
    }

  }

  @Override
  public String toString() {
    return "ZMTPConfig{" +
           "protocol=" + protocol +
           ", interop=" + interop +
           ", socketType=" + socketType +
           ", localIdentity=" + localIdentity +
           ", encoder=" + encoder +
           ", decoder=" + decoder +
           '}';
  }

  private static class ZMTPEncoderClassFactory implements ZMTPEncoder.Factory {

    private final Constructor<? extends ZMTPEncoder> constructor;

    public ZMTPEncoderClassFactory(final Class<? extends ZMTPEncoder> encoder) {
      checkNotNull(encoder, "encoder");
      try {
        constructor = encoder.getDeclaredConstructor();
      } catch (NoSuchMethodException e) {
        throw new IllegalArgumentException("Class must have default constructor: " + encoder);
      }
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
    }

    @Override
    public ZMTPEncoder encoder(final ZMTPConfig config) {
      try {
        return constructor.newInstance();
      } catch (InstantiationException e) {
        throw new RuntimeException(e);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public String toString() {
      return "ZMTPEncoderClassFactory{" +
             "constructor=" + constructor +
             '}';
    }
  }

  private static class ZMTPDecoderClassFactory implements ZMTPDecoder.Factory {

    private final Constructor<? extends ZMTPDecoder> constructor;

    public ZMTPDecoderClassFactory(final Class<? extends ZMTPDecoder> decoder) {
      checkNotNull(decoder, "decoder");
      try {
        constructor = decoder.getDeclaredConstructor();
      } catch (NoSuchMethodException e) {
        throw new IllegalArgumentException("Class must have default constructor: " + decoder);
      }
      if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
      }
    }

    @Override
    public ZMTPDecoder decoder(final ZMTPConfig config) {
      try {
        return constructor.newInstance();
      } catch (ReflectiveOperationException e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public String toString() {
      return "ZMTPDecoderClassFactory{" +
             "constructor=" + constructor +
             '}';
    }
  }
}

@@ -6562,27 +6562,188 @@
 ch (
-ReflectiveOperation
+InstantiationException e) %7B%0A        throw new RuntimeException(e);%0A      %7D catch (IllegalAccessException e) %7B%0A        throw new RuntimeException(e);%0A      %7D catch (InvocationTarget
 Exce

fix java 6 compat