@Override
        public Iterator<SingularAttribute<? super T, ?>> iterator() {
            return attributes.iterator();
        }

package org.springframework.data.jpa.repository.support;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.persistence.IdClass;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
import javax.persistence.metamodel.Type.PersistenceType;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.util.JpaMetamodel;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.data.util.ProxyUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Implementation of {@link org.springframework.data.repository.core.EntityInformation} that uses JPA {@link Metamodel}
 * to find the domain class' id field.
 *
 * @author Oliver Gierke
 * @author Thomas Darimont
 * @author Christoph Strobl
 * @author Mark Paluch
 * @author Jens Schauder
 */
public class JpaMetamodelEntityInformation<T, ID> extends JpaEntityInformationSupport<T, ID> {

    private final IdMetadata<T> idMetadata;

    private final Optional<SingularAttribute<? super T, ?>> versionAttribute;

    private final Metamodel metamodel;

    @Nullable
    private final String entityName;

    /**
     * Creates a new {@link JpaMetamodelEntityInformation} for the given domain class and {@link Metamodel}.
     *
     * @param domainClass must not be {@literal null}.
     * @param metamodel must not be {@literal null}.
     */
    public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel) {
        super(domainClass);
        Assert.notNull(metamodel, "Metamodel must not be null!");
        this.metamodel = metamodel;
        ManagedType<T> type = metamodel.managedType(domainClass);
        if (type == null) {
            throw new IllegalArgumentException("The given domain class can not be found in the given Metamodel!");
        }
        this.entityName = type instanceof EntityType ? ((EntityType<?>) type).getName() : null;
        if (!(type instanceof IdentifiableType)) {
            throw new IllegalArgumentException("The given domain class does not contain an id attribute!");
        }
        IdentifiableType<T> identifiableType = (IdentifiableType<T>) type;
        this.idMetadata = new IdMetadata<>(identifiableType, PersistenceProvider.fromMetamodel(metamodel));
        this.versionAttribute = findVersionAttribute(identifiableType, metamodel);
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformationSupport#getEntityName()
	 */
    @Override
    public String getEntityName() {
        return entityName != null ? entityName : super.getEntityName();
    }

    /**
     * Returns the version attribute of the given {@link ManagedType} or {@literal null} if none available.
     *
     * @param type must not be {@literal null}.
     * @param metamodel must not be {@literal null}.
     * @return
     */
    @SuppressWarnings("unchecked")
    private static <T> Optional<SingularAttribute<? super T, ?>> findVersionAttribute(IdentifiableType<T> type, Metamodel metamodel) {
        try {
            return Optional.ofNullable(type.getVersion(Object.class));
        } catch (IllegalArgumentException o_O) {
            // Needs workarounds as the method is implemented with a strict type check on e.g. Hibernate < 4.3
        }
        Set<SingularAttribute<? super T, ?>> attributes = type.getSingularAttributes();
        for (SingularAttribute<? super T, ?> attribute : attributes) {
            if (attribute.isVersion()) {
                return Optional.of(attribute);
            }
        }
        Class<?> superType = type.getJavaType().getSuperclass();
        if (!JpaMetamodel.of(metamodel).isJpaManaged(superType)) {
            return Optional.empty();
        }
        ManagedType<?> managedSuperType = metamodel.managedType(superType);
        if (!(managedSuperType instanceof IdentifiableType)) {
            return Optional.empty();
        }
        return findVersionAttribute((IdentifiableType<T>) managedSuperType, metamodel);
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object)
	 */
    @Override
    @Nullable
    @SuppressWarnings("unchecked")
    public ID getId(T entity) {
        // check if this is a proxy. If so use Proxy mechanics to access the id.
        PersistenceProvider persistenceProvider = PersistenceProvider.fromMetamodel(metamodel);
        if (persistenceProvider.shouldUseAccessorFor(entity)) {
            return (ID) persistenceProvider.getIdentifierFrom(entity);
        }
        // if not a proxy use Spring mechanics to access the id.
        BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
        if (idMetadata.hasSimpleId()) {
            return (ID) entityWrapper.getPropertyValue(idMetadata.getSimpleIdAttribute().getName());
        }
        BeanWrapper idWrapper = new IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(idMetadata.getType(), metamodel);
        boolean partialIdValueFound = false;
        for (SingularAttribute<? super T, ?> attribute : idMetadata) {
            Object propertyValue = entityWrapper.getPropertyValue(attribute.getName());
            if (propertyValue != null) {
                partialIdValueFound = true;
            }
            idWrapper.setPropertyValue(attribute.getName(), propertyValue);
        }
        return partialIdValueFound ? (ID) idWrapper.getWrappedInstance() : null;
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.core.EntityInformation#getIdType()
	 */
    @Override
    @SuppressWarnings("unchecked")
    public Class<ID> getIdType() {
        return (Class<ID>) idMetadata.getType();
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getIdAttribute()
	 */
    @Override
    public SingularAttribute<? super T, ?> getIdAttribute() {
        return idMetadata.getSimpleIdAttribute();
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#hasCompositeId()
	 */
    @Override
    public boolean hasCompositeId() {
        return !idMetadata.hasSimpleId();
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getIdAttributeNames()
	 */
    @Override
    public Iterable<String> getIdAttributeNames() {
        List<String> attributeNames = new ArrayList<>(idMetadata.attributes.size());
        for (SingularAttribute<? super T, ?> attribute : idMetadata.attributes) {
            attributeNames.add(attribute.getName());
        }
        return attributeNames;
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getCompositeIdAttributeValue(java.lang.Object, java.lang.String)
	 */
    @Override
    public Object getCompositeIdAttributeValue(Object id, String idAttribute) {
        Assert.isTrue(hasCompositeId(), "Model must have a composite Id!");
        return new DirectFieldAccessFallbackBeanWrapper(id).getPropertyValue(idAttribute);
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.core.support.AbstractEntityInformation#isNew(java.lang.Object)
	 */
    @Override
    public boolean isNew(T entity) {
        if (!versionAttribute.isPresent() || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
            return super.isNew(entity);
        }
        BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
        return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
    }

    /**
     * Simple value object to encapsulate id specific metadata.
     *
     * @author Oliver Gierke
     * @author Thomas Darimont
     */
    private static class IdMetadata<T> implements Iterable<SingularAttribute<? super T, ?>> {

        private final IdentifiableType<T> type;

        private final Set<SingularAttribute<? super T, ?>> idClassAttributes;

        private final Set<SingularAttribute<? super T, ?>> attributes;

        @Nullable
        private Class<?> idType;

        @SuppressWarnings("unchecked")
        IdMetadata(IdentifiableType<T> source, PersistenceProvider persistenceProvider) {
            this.type = source;
            this.idClassAttributes = persistenceProvider.getIdClassAttributes(source);
            this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute() ? Collections.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
        }

        boolean hasSimpleId() {
            return idClassAttributes.isEmpty() && attributes.size() == 1;
        }

        public Class<?> getType() {
            if (idType != null) {
                return idType;
            }
            // lazy initialization of idType field with tolerable benign data-race
            this.idType = tryExtractIdTypeWithFallbackToIdTypeLookup();
            if (this.idType == null) {
                throw new IllegalStateException("Cannot resolve Id type from " + type);
            }
            return this.idType;
        }

        @Nullable
        private Class<?> tryExtractIdTypeWithFallbackToIdTypeLookup() {
            try {
                Class<?> idClassType = lookupIdClass(type);
                if (idClassType != null) {
                    return idClassType;
                }
                Type<?> idType = type.getIdType();
                return idType == null ? null : idType.getJavaType();
            } catch (IllegalStateException e) {
                return null;
            }
        }

        @Nullable
        private static Class<?> lookupIdClass(IdentifiableType<?> type) {
            IdClass annotation = type.getJavaType() != null ? AnnotationUtils.findAnnotation(type.getJavaType(), IdClass.class) : null;
            return annotation == null ? null : annotation.value();
        }

        SingularAttribute<? super T, ?> getSimpleIdAttribute() {
            return attributes.iterator().next();
        }

        /*
		 * (non-Javadoc)
		 * @see java.lang.Iterable#iterator()
		 */

    }

    /**
     * Custom extension of {@link DirectFieldAccessFallbackBeanWrapper} that allows to derive the identifier if composite
     * keys with complex key attribute types (e.g. types that are annotated with {@code @Entity} themselves) are used.
     *
     * @author Thomas Darimont
     */
    private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper extends DirectFieldAccessFallbackBeanWrapper {

        private final Metamodel metamodel;

        private final JpaMetamodel jpaMetamodel;

        IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(Class<?> type, Metamodel metamodel) {
            super(type);
            this.metamodel = metamodel;
            this.jpaMetamodel = JpaMetamodel.of(metamodel);
        }

        /**
         * In addition to the functionality described in {@link BeanWrapperImpl} it is checked whether we have a nested
         * entity that is part of the id key. If this is the case, we need to derive the identifier of the nested entity.
         */
        @Override
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public void setPropertyValue(String propertyName, @Nullable Object value) {
            if (!isIdentifierDerivationNecessary(value)) {
                super.setPropertyValue(propertyName, value);
                return;
            }
            // Derive the identifier from the nested entity that is part of the composite key.
            JpaMetamodelEntityInformation nestedEntityInformation = new JpaMetamodelEntityInformation(ProxyUtils.getUserClass(value), this.metamodel);
            if (!nestedEntityInformation.getJavaType().isAnnotationPresent(IdClass.class)) {
                Object nestedIdPropertyValue = new DirectFieldAccessFallbackBeanWrapper(value).getPropertyValue(nestedEntityInformation.getRequiredIdAttribute().getName());
                super.setPropertyValue(propertyName, nestedIdPropertyValue);
                return;
            }
            // We have an IdClass property, we need to inspect the current value in order to map potentially multiple id
            // properties correctly.
            BeanWrapper sourceIdValueWrapper = new DirectFieldAccessFallbackBeanWrapper(value);
            BeanWrapper targetIdClassTypeWrapper = new BeanWrapperImpl(nestedEntityInformation.getIdType());
            for (String idAttributeName : (Iterable<String>) nestedEntityInformation.getIdAttributeNames()) {
                targetIdClassTypeWrapper.setPropertyValue(idAttributeName, extractActualIdPropertyValue(sourceIdValueWrapper, idAttributeName));
            }
            super.setPropertyValue(propertyName, targetIdClassTypeWrapper.getWrappedInstance());
        }

        @Nullable
        private Object extractActualIdPropertyValue(BeanWrapper sourceIdValueWrapper, String idAttributeName) {
            Object idPropertyValue = sourceIdValueWrapper.getPropertyValue(idAttributeName);
            if (idPropertyValue != null) {
                Class<?> idPropertyValueType = idPropertyValue.getClass();
                if (!jpaMetamodel.isJpaManaged(idPropertyValueType)) {
                    return idPropertyValue;
                }
                return new DirectFieldAccessFallbackBeanWrapper(idPropertyValue).getPropertyValue(tryFindSingularIdAttributeNameOrUseFallback(idPropertyValueType, idAttributeName));
            }
            return null;
        }

        private String tryFindSingularIdAttributeNameOrUseFallback(Class<?> idPropertyValueType, String fallbackIdTypePropertyName) {
            ManagedType<?> idPropertyType = metamodel.managedType(idPropertyValueType);
            for (SingularAttribute<?, ?> sa : idPropertyType.getSingularAttributes()) {
                if (sa.isId()) {
                    return sa.getName();
                }
            }
            return fallbackIdTypePropertyName;
        }

        /**
         * @param value
         * @return {@literal true} if the given value is not {@literal null} and a mapped persistable entity otherwise
         *         {@literal false}
         */
        private boolean isIdentifierDerivationNecessary(@Nullable Object value) {
            if (value == null) {
                return false;
            }
            Class<?> userClass = ProxyUtils.getUserClass(value);
            if (!this.jpaMetamodel.isJpaManaged(userClass)) {
                return false;
            }
            ManagedType<?> managedType = this.metamodel.managedType(userClass);
            if (managedType == null) {
                throw new IllegalStateException("ManagedType must not be null. We checked that it exists before.");
            }
            return managedType.getPersistenceType() == PersistenceType.ENTITY;
        }
    }
}


try {
            return Optional.ofNullable(type.getVersion(Object.class));
        } catch (IllegalArgumentException o_O) {
            // Needs workarounds as the method is implemented with a strict type check on e.g. Hibernate < 4.3
        }

package org.springframework.data.jpa.repository.support;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.persistence.IdClass;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
import javax.persistence.metamodel.Type.PersistenceType;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.util.JpaMetamodel;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.data.util.ProxyUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Implementation of {@link org.springframework.data.repository.core.EntityInformation} that uses JPA {@link Metamodel}
 * to find the domain class' id field.
 *
 * @author Oliver Gierke
 * @author Thomas Darimont
 * @author Christoph Strobl
 * @author Mark Paluch
 * @author Jens Schauder
 */
public class JpaMetamodelEntityInformation<T, ID> extends JpaEntityInformationSupport<T, ID> {

    private final IdMetadata<T> idMetadata;

    private final Optional<SingularAttribute<? super T, ?>> versionAttribute;

    private final Metamodel metamodel;

    @Nullable
    private final String entityName;

    /**
     * Creates a new {@link JpaMetamodelEntityInformation} for the given domain class and {@link Metamodel}.
     *
     * @param domainClass must not be {@literal null}.
     * @param metamodel must not be {@literal null}.
     */
    public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel) {
        super(domainClass);
        Assert.notNull(metamodel, "Metamodel must not be null!");
        this.metamodel = metamodel;
        ManagedType<T> type = metamodel.managedType(domainClass);
        if (type == null) {
            throw new IllegalArgumentException("The given domain class can not be found in the given Metamodel!");
        }
        this.entityName = type instanceof EntityType ? ((EntityType<?>) type).getName() : null;
        if (!(type instanceof IdentifiableType)) {
            throw new IllegalArgumentException("The given domain class does not contain an id attribute!");
        }
        IdentifiableType<T> identifiableType = (IdentifiableType<T>) type;
        this.idMetadata = new IdMetadata<>(identifiableType, PersistenceProvider.fromMetamodel(metamodel));
        this.versionAttribute = findVersionAttribute(identifiableType, metamodel);
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformationSupport#getEntityName()
	 */
    @Override
    public String getEntityName() {
        return entityName != null ? entityName : super.getEntityName();
    }

    /**
     * Returns the version attribute of the given {@link ManagedType} or {@literal null} if none available.
     *
     * @param type must not be {@literal null}.
     * @param metamodel must not be {@literal null}.
     * @return
     */
    @SuppressWarnings("unchecked")
    private static <T> Optional<SingularAttribute<? super T, ?>> findVersionAttribute(IdentifiableType<T> type, Metamodel metamodel) {

        Set<SingularAttribute<? super T, ?>> attributes = type.getSingularAttributes();
        for (SingularAttribute<? super T, ?> attribute : attributes) {
            if (attribute.isVersion()) {
                return Optional.of(attribute);
            }
        }
        Class<?> superType = type.getJavaType().getSuperclass();
        if (!JpaMetamodel.of(metamodel).isJpaManaged(superType)) {
            return Optional.empty();
        }
        ManagedType<?> managedSuperType = metamodel.managedType(superType);
        if (!(managedSuperType instanceof IdentifiableType)) {
            return Optional.empty();
        }
        return findVersionAttribute((IdentifiableType<T>) managedSuperType, metamodel);
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object)
	 */
    @Override
    @Nullable
    @SuppressWarnings("unchecked")
    public ID getId(T entity) {
        // check if this is a proxy. If so use Proxy mechanics to access the id.
        PersistenceProvider persistenceProvider = PersistenceProvider.fromMetamodel(metamodel);
        if (persistenceProvider.shouldUseAccessorFor(entity)) {
            return (ID) persistenceProvider.getIdentifierFrom(entity);
        }
        // if not a proxy use Spring mechanics to access the id.
        BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
        if (idMetadata.hasSimpleId()) {
            return (ID) entityWrapper.getPropertyValue(idMetadata.getSimpleIdAttribute().getName());
        }
        BeanWrapper idWrapper = new IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(idMetadata.getType(), metamodel);
        boolean partialIdValueFound = false;
        for (SingularAttribute<? super T, ?> attribute : idMetadata) {
            Object propertyValue = entityWrapper.getPropertyValue(attribute.getName());
            if (propertyValue != null) {
                partialIdValueFound = true;
            }
            idWrapper.setPropertyValue(attribute.getName(), propertyValue);
        }
        return partialIdValueFound ? (ID) idWrapper.getWrappedInstance() : null;
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.core.EntityInformation#getIdType()
	 */
    @Override
    @SuppressWarnings("unchecked")
    public Class<ID> getIdType() {
        return (Class<ID>) idMetadata.getType();
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getIdAttribute()
	 */
    @Override
    public SingularAttribute<? super T, ?> getIdAttribute() {
        return idMetadata.getSimpleIdAttribute();
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#hasCompositeId()
	 */
    @Override
    public boolean hasCompositeId() {
        return !idMetadata.hasSimpleId();
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getIdAttributeNames()
	 */
    @Override
    public Iterable<String> getIdAttributeNames() {
        List<String> attributeNames = new ArrayList<>(idMetadata.attributes.size());
        for (SingularAttribute<? super T, ?> attribute : idMetadata.attributes) {
            attributeNames.add(attribute.getName());
        }
        return attributeNames;
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getCompositeIdAttributeValue(java.lang.Object, java.lang.String)
	 */
    @Override
    public Object getCompositeIdAttributeValue(Object id, String idAttribute) {
        Assert.isTrue(hasCompositeId(), "Model must have a composite Id!");
        return new DirectFieldAccessFallbackBeanWrapper(id).getPropertyValue(idAttribute);
    }

    /*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.core.support.AbstractEntityInformation#isNew(java.lang.Object)
	 */
    @Override
    public boolean isNew(T entity) {
        if (!versionAttribute.isPresent() || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
            return super.isNew(entity);
        }
        BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
        return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
    }

    /**
     * Simple value object to encapsulate id specific metadata.
     *
     * @author Oliver Gierke
     * @author Thomas Darimont
     */
    private static class IdMetadata<T> implements Iterable<SingularAttribute<? super T, ?>> {

        private final IdentifiableType<T> type;

        private final Set<SingularAttribute<? super T, ?>> idClassAttributes;

        private final Set<SingularAttribute<? super T, ?>> attributes;

        @Nullable
        private Class<?> idType;

        @SuppressWarnings("unchecked")
        IdMetadata(IdentifiableType<T> source, PersistenceProvider persistenceProvider) {
            this.type = source;
            this.idClassAttributes = persistenceProvider.getIdClassAttributes(source);
            this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute() ? Collections.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
        }

        boolean hasSimpleId() {
            return idClassAttributes.isEmpty() && attributes.size() == 1;
        }

        public Class<?> getType() {
            if (idType != null) {
                return idType;
            }
            // lazy initialization of idType field with tolerable benign data-race
            this.idType = tryExtractIdTypeWithFallbackToIdTypeLookup();
            if (this.idType == null) {
                throw new IllegalStateException("Cannot resolve Id type from " + type);
            }
            return this.idType;
        }

        @Nullable
        private Class<?> tryExtractIdTypeWithFallbackToIdTypeLookup() {
            try {
                Class<?> idClassType = lookupIdClass(type);
                if (idClassType != null) {
                    return idClassType;
                }
                Type<?> idType = type.getIdType();
                return idType == null ? null : idType.getJavaType();
            } catch (IllegalStateException e) {
                return null;
            }
        }

        @Nullable
        private static Class<?> lookupIdClass(IdentifiableType<?> type) {
            IdClass annotation = type.getJavaType() != null ? AnnotationUtils.findAnnotation(type.getJavaType(), IdClass.class) : null;
            return annotation == null ? null : annotation.value();
        }

        SingularAttribute<? super T, ?> getSimpleIdAttribute() {
            return attributes.iterator().next();
        }

        /*
		 * (non-Javadoc)
		 * @see java.lang.Iterable#iterator()
		 */
        @Override
        public Iterator<SingularAttribute<? super T, ?>> iterator() {
            return attributes.iterator();
        }
    }

    /**
     * Custom extension of {@link DirectFieldAccessFallbackBeanWrapper} that allows to derive the identifier if composite
     * keys with complex key attribute types (e.g. types that are annotated with {@code @Entity} themselves) are used.
     *
     * @author Thomas Darimont
     */
    private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper extends DirectFieldAccessFallbackBeanWrapper {

        private final Metamodel metamodel;

        private final JpaMetamodel jpaMetamodel;

        IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(Class<?> type, Metamodel metamodel) {
            super(type);
            this.metamodel = metamodel;
            this.jpaMetamodel = JpaMetamodel.of(metamodel);
        }

        /**
         * In addition to the functionality described in {@link BeanWrapperImpl} it is checked whether we have a nested
         * entity that is part of the id key. If this is the case, we need to derive the identifier of the nested entity.
         */
        @Override
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public void setPropertyValue(String propertyName, @Nullable Object value) {
            if (!isIdentifierDerivationNecessary(value)) {
                super.setPropertyValue(propertyName, value);
                return;
            }
            // Derive the identifier from the nested entity that is part of the composite key.
            JpaMetamodelEntityInformation nestedEntityInformation = new JpaMetamodelEntityInformation(ProxyUtils.getUserClass(value), this.metamodel);
            if (!nestedEntityInformation.getJavaType().isAnnotationPresent(IdClass.class)) {
                Object nestedIdPropertyValue = new DirectFieldAccessFallbackBeanWrapper(value).getPropertyValue(nestedEntityInformation.getRequiredIdAttribute().getName());
                super.setPropertyValue(propertyName, nestedIdPropertyValue);
                return;
            }
            // We have an IdClass property, we need to inspect the current value in order to map potentially multiple id
            // properties correctly.
            BeanWrapper sourceIdValueWrapper = new DirectFieldAccessFallbackBeanWrapper(value);
            BeanWrapper targetIdClassTypeWrapper = new BeanWrapperImpl(nestedEntityInformation.getIdType());
            for (String idAttributeName : (Iterable<String>) nestedEntityInformation.getIdAttributeNames()) {
                targetIdClassTypeWrapper.setPropertyValue(idAttributeName, extractActualIdPropertyValue(sourceIdValueWrapper, idAttributeName));
            }
            super.setPropertyValue(propertyName, targetIdClassTypeWrapper.getWrappedInstance());
        }

        @Nullable
        private Object extractActualIdPropertyValue(BeanWrapper sourceIdValueWrapper, String idAttributeName) {
            Object idPropertyValue = sourceIdValueWrapper.getPropertyValue(idAttributeName);
            if (idPropertyValue != null) {
                Class<?> idPropertyValueType = idPropertyValue.getClass();
                if (!jpaMetamodel.isJpaManaged(idPropertyValueType)) {
                    return idPropertyValue;
                }
                return new DirectFieldAccessFallbackBeanWrapper(idPropertyValue).getPropertyValue(tryFindSingularIdAttributeNameOrUseFallback(idPropertyValueType, idAttributeName));
            }
            return null;
        }

        private String tryFindSingularIdAttributeNameOrUseFallback(Class<?> idPropertyValueType, String fallbackIdTypePropertyName) {
            ManagedType<?> idPropertyType = metamodel.managedType(idPropertyValueType);
            for (SingularAttribute<?, ?> sa : idPropertyType.getSingularAttributes()) {
                if (sa.isId()) {
                    return sa.getName();
                }
            }
            return fallbackIdTypePropertyName;
        }

        /**
         * @param value
         * @return {@literal true} if the given value is not {@literal null} and a mapped persistable entity otherwise
         *         {@literal false}
         */
        private boolean isIdentifierDerivationNecessary(@Nullable Object value) {
            if (value == null) {
                return false;
            }
            Class<?> userClass = ProxyUtils.getUserClass(value);
            if (!this.jpaMetamodel.isJpaManaged(userClass)) {
                return false;
            }
            ManagedType<?> managedType = this.metamodel.managedType(userClass);
            if (managedType == null) {
                throw new IllegalStateException("ManagedType must not be null. We checked that it exists before.");
            }
            return managedType.getPersistenceType() == PersistenceType.ENTITY;
        }
    }
}


package org.mule.runtime.deployment.model.api.policy;

import org.mule.api.annotation.NoImplement;
import org.mule.runtime.deployment.model.api.plugin.ArtifactPlugin;
import org.mule.runtime.module.artifact.api.Artifact;
import java.util.List;

/**
 * Represents a policy template artifact.
 *
 * @since 4.0
 */
@NoImplement
public interface PolicyTemplate extends Artifact<PolicyTemplateDescriptor> {

    /**
     * Disposes the artifact releasing any held resources
     */

    /**
     * @return plugins deployed only inside the policy template
     */
    List<ArtifactPlugin> getArtifactPlugins();

    /**
     * @return plugins the policy depends on
     */
    List<ArtifactPlugin> getOwnArtifactPlugins();
}


private T cycle(final int direction) {
        try {
            return mList.get(mIndex = computeIndex(direction));
        } catch (final Exception ex) {
            throw new NoSuchElementException(ex);
        }
    }

package com.keenwrite.util;

import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

/**
 * Responsible for iterating over a list either forwards or backwards. When
 * the iterator reaches the last element in the list, the next element will
 * be the first. When the iterator reaches the first element in the list,
 * the previous element will be the last.
 * <p>
 * Due to the ability to move forwards and backwards through the list, rather
 * than force client classes to track the list index independently, this
 * iterator provides an accessor to the index. The index is zero-based.
 * </p>
 *
 * @param <T> The type of list to be cycled.
 */
public final class CyclicIterator<T> implements ListIterator<T> {

    private final List<T> mList;

    /**
     * Initialize to an invalid index so that the first calls to either
     * {@link #previous()} or {@link #next()} will return the starting or ending
     * element.
     */
    private int mIndex = -1;

    /**
     * Creates an iterator that cycles indefinitely through the given list.
     *
     * @param list The list to cycle through indefinitely.
     */
    public CyclicIterator(final List<T> list) {
        mList = list;
    }

    /**
     * @return {@code true} if there is at least one element.
     */
    @Override
    public boolean hasNext() {
        return !mList.isEmpty();
    }

    /**
     * @return {@code true} if there is at least one element.
     */
    @Override
    public boolean hasPrevious() {
        return !mList.isEmpty();
    }

    @Override
    public int nextIndex() {
        return computeIndex(+1);
    }

    @Override
    public int previousIndex() {
        return computeIndex(-1);
    }

    @Override
    public void remove() {
        mList.remove(mIndex);
    }

    @Override
    public void set(final T t) {
        mList.set(mIndex, t);
    }

    @Override
    public void add(final T t) {
        mList.add(mIndex, t);
    }

    /**
     * Returns the next item in the list, which will cycle to the first
     * item as necessary.
     *
     * @return The next item in the list, cycling to the start if needed.
     */
    @Override
    public T next() {
        return cycle(+1);
    }

    /**
     * Returns the previous item in the list, which will cycle to the last
     * item as necessary.
     *
     * @return The previous item in the list, cycling to the end if needed.
     */
    @Override
    public T previous() {
        return cycle(-1);
    }

    /**
     * Cycles to the next or previous element, depending on the direction value.
     *
     * @param direction Use -1 for previous, +1 for next.
     * @return The next or previous item in the list.
     */

    /**
     * Returns the index of the value retrieved from the most recent call to
     * either {@link #previous()} or {@link #next()}.
     *
     * @return The list item index or -1 if no calls have been made to retrieve
     * an item from the list.
     */
    public int getIndex() {
        return mIndex;
    }

    private int computeIndex(final int direction) {
        final var i = mIndex + direction;
        final var size = mList.size();
        final var result = i < 0 ? size - 1 : size == 0 ? 0 : i % size;
        // Ensure the invariant holds.
        assert 0 <= result && result < size || size == 0 && result <= 0;
        return result;
    }
}


try {
            return mList.get(mIndex = computeIndex(direction));
        } catch (final Exception ex) {
            throw new NoSuchElementException(ex);
        }

package com.keenwrite.util;

import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

/**
 * Responsible for iterating over a list either forwards or backwards. When
 * the iterator reaches the last element in the list, the next element will
 * be the first. When the iterator reaches the first element in the list,
 * the previous element will be the last.
 * <p>
 * Due to the ability to move forwards and backwards through the list, rather
 * than force client classes to track the list index independently, this
 * iterator provides an accessor to the index. The index is zero-based.
 * </p>
 *
 * @param <T> The type of list to be cycled.
 */
public final class CyclicIterator<T> implements ListIterator<T> {

    private final List<T> mList;

    /**
     * Initialize to an invalid index so that the first calls to either
     * {@link #previous()} or {@link #next()} will return the starting or ending
     * element.
     */
    private int mIndex = -1;

    /**
     * Creates an iterator that cycles indefinitely through the given list.
     *
     * @param list The list to cycle through indefinitely.
     */
    public CyclicIterator(final List<T> list) {
        mList = list;
    }

    /**
     * @return {@code true} if there is at least one element.
     */
    @Override
    public boolean hasNext() {
        return !mList.isEmpty();
    }

    /**
     * @return {@code true} if there is at least one element.
     */
    @Override
    public boolean hasPrevious() {
        return !mList.isEmpty();
    }

    @Override
    public int nextIndex() {
        return computeIndex(+1);
    }

    @Override
    public int previousIndex() {
        return computeIndex(-1);
    }

    @Override
    public void remove() {
        mList.remove(mIndex);
    }

    @Override
    public void set(final T t) {
        mList.set(mIndex, t);
    }

    @Override
    public void add(final T t) {
        mList.add(mIndex, t);
    }

    /**
     * Returns the next item in the list, which will cycle to the first
     * item as necessary.
     *
     * @return The next item in the list, cycling to the start if needed.
     */
    @Override
    public T next() {
        return cycle(+1);
    }

    /**
     * Returns the previous item in the list, which will cycle to the last
     * item as necessary.
     *
     * @return The previous item in the list, cycling to the end if needed.
     */
    @Override
    public T previous() {
        return cycle(-1);
    }

    /**
     * Cycles to the next or previous element, depending on the direction value.
     *
     * @param direction Use -1 for previous, +1 for next.
     * @return The next or previous item in the list.
     */
    private T cycle(final int direction) {

    }

    /**
     * Returns the index of the value retrieved from the most recent call to
     * either {@link #previous()} or {@link #next()}.
     *
     * @return The list item index or -1 if no calls have been made to retrieve
     * an item from the list.
     */
    public int getIndex() {
        return mIndex;
    }

    private int computeIndex(final int direction) {
        final var i = mIndex + direction;
        final var size = mList.size();
        final var result = i < 0 ? size - 1 : size == 0 ? 0 : i % size;
        // Ensure the invariant holds.
        assert 0 <= result && result < size || size == 0 && result <= 0;
        return result;
    }
}


boolean matches(String name, AsmMetric metric);

package com.alibaba.jstorm.metric;

import com.alibaba.jstorm.common.metric.AsmMetric;
import java.io.Serializable;

/**
 * @author Cody (example@nnthink.com)
 * @since 2.0.5
 */
public interface AsmMetricFilter extends Serializable {

    /**
     * Matches all metrics, regardless of type or name.
     */
    AsmMetricFilter ALL = new AsmMetricFilter() {

        private static final long serialVersionUID = 7089987006352295530L;

        @Override
        public boolean matches(String name, AsmMetric metric) {
            return true;
        }
    };

    /**
     * Returns {@code true} if the metric matches the filter; {@code false} otherwise.
     *
     * @param name the metric node
     * @param metric the metric
     * @return {@code true} if the metric matches the filter
     */


for (int i = 0; i < 4; i++) {
            TDFDataSource src = new TDFDataSource(reader, i, Character.toString(nucleotides[i]), genome);
            baseSources.put(nucleotides[i], src);
        }

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.broad.igv.sam;

import org.broad.igv.feature.Locus;
import org.broad.igv.feature.LocusScore;
import org.broad.igv.feature.genome.Genome;
import org.broad.igv.renderer.DataRange;
import org.broad.igv.renderer.SequenceRenderer;
import org.broad.igv.tdf.TDFDataSource;
import org.broad.igv.tdf.TDFReader;
import org.broad.igv.track.*;
import org.broad.igv.ui.panel.IGVPopupMenu;
import org.broad.igv.ui.panel.ReferenceFrame;
import org.broad.igv.util.ResourceLocator;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Developmental track to explore display of conservation scores.
 *
 * @author jrobinso
 */
public class EWigTrack extends AbstractTrack {

    char[] nucleotides = { 'A', 'C', 'G', 'T' };

    public static Color grey1 = new Color(230, 230, 230);

    Map<Character, TDFDataSource> baseSources;

    TDFDataSource tdfSource;

    // TODO -- memory leak.  This needs to get cleared when the gene list changes
    private HashMap<String, LoadedInterval> loadedIntervalCache = new HashMap(200);

    public EWigTrack(ResourceLocator locator, Genome genome) {
        super(locator);
        TDFReader reader = TDFReader.getReader(locator.getPath());
        tdfSource = new TDFDataSource(reader, 4, "Pi", genome);
        setDataRange(new DataRange(0, 0, 10));
        baseSources = new HashMap();

    }

    public EWigTrack() {
    }

    @Override
    public boolean isNumeric() {
        return true;
    }

    @Override
    public boolean isReadyToPaint(ReferenceFrame frame) {
        final String chr = frame.getChrName();
        final int start = (int) frame.getOrigin();
        final int end = (int) frame.getEnd();
        final int zoom = frame.getZoom();
        LoadedInterval loadedInterval = loadedIntervalCache.get(frame.getName());
        return (loadedInterval != null && loadedInterval.contains(chr, start, end, zoom));
    }

    @Override
    public void load(ReferenceFrame context) {
        final String chr = context.getChrName();
        int start = (int) context.getOrigin();
        int end = (int) context.getEnd();
        final int zoom = context.getZoom();
        // Expand start and end a bit for panning
        int w = end - start;
        start -= w / 2;
        end += w / 2;
        List<LocusScore> scores = tdfSource.getSummaryScoresForRange(chr, (int) start, end, zoom);
        Map<Character, List<LocusScore>> nScores = new HashMap();
        for (Character c : nucleotides) {
            nScores.put(c, baseSources.get(c).getSummaryScoresForRange(chr, (int) start, end, zoom));
        }
        loadedIntervalCache.put(context.getName(), new LoadedInterval(chr, start, end, zoom, scores, nScores));
    }

    public void render(RenderContext context, Rectangle rect) {
        paint(context, rect);
    }

    public boolean isLogNormalized() {
        return false;
    }

    public float getRegionScore(String chr, int start, int end, int zoom, RegionScoreType type, String frameName) {
        return 0;
    }

    private void paint(RenderContext context, Rectangle rect) {
        ReferenceFrame frame = context.getReferenceFrame();
        final String chr = frame.getChrName();
        final int start = (int) frame.getOrigin();
        final int end = (int) frame.getEnd();
        final int zoom = frame.getZoom();
        LoadedInterval loadedInterval = loadedIntervalCache.get(frame.getName());
        if (loadedInterval == null || !loadedInterval.contains(chr, start, end, zoom)) {
            return;
        }
        // The total score
        List<LocusScore> scores = loadedInterval.scores;
        Map<Character, List<LocusScore>> nScores = loadedInterval.baseScores;
        Map<Character, Color> nucleotideColors = SequenceRenderer.getNucleotideColors();
        for (int idx = 0; idx < scores.size(); idx++) {
            LocusScore score = scores.get(idx);
            int startPosition = score.getStart();
            int endPosition = score.getEnd();
            int pX = (int) (rect.getX() + (startPosition - context.getOrigin()) / context.getScale());
            int dX = Math.max(1, (int) (rect.getX() + (endPosition - context.getOrigin()) / context.getScale()) - pX);
            if (dX > 4) {
                dX -= 2;
                pX++;
            }
            if (pX + dX < 0) {
                continue;
            } else if (pX > context.getVisibleRect().getMaxX()) {
                break;
            }
            float totalCount = score.getScore();
            int pY = (int) rect.getMaxY() - 1;
            float dataMax = getDataRange().getMaximum();
            double height = (totalCount * rect.getHeight() / dataMax);
            height = Math.min(height, rect.height - 1);
            for (char c : nucleotides) {
                try {
                    LocusScore ns = nScores.get(c).get(idx);
                    float count = ns.getScore() * totalCount;
                    Graphics2D tGraphics = context.getGraphic2DForColor(nucleotideColors.get(c));
                    int h = (int) Math.round(count * height / totalCount);
                    h = Math.min(pY - rect.y, h);
                    int baseY = (int) (pY - h);
                    if (h > 0) {
                        tGraphics.fillRect(pX, baseY, dX, h);
                    }
                    pY = baseY;
                } catch (Exception e) {
                    // To change body of catch statement use File | Settings | File Templates.
                    e.printStackTrace();
                }
            }
        }
        // Draw border
        context.getGraphic2DForColor(Color.gray).drawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
        // Draw scale
    }

    public JPopupMenu getPopupMenu(final MouseEvent evt) {
        JPopupMenu popupMenu = new IGVPopupMenu();
        JLabel popupTitle = new JLabel("  " + getName(), JLabel.CENTER);
        Font newFont = popupMenu.getFont().deriveFont(Font.BOLD, 12);
        popupTitle.setFont(newFont);
        if (popupTitle != null) {
            popupMenu.add(popupTitle);
        }
        popupMenu.addSeparator();
        ArrayList<Track> tmp = new ArrayList();
        tmp.add(this);
        popupMenu.add(TrackMenuUtils.getTrackRenameItem(tmp));
        popupMenu.add(TrackMenuUtils.getChangeTrackHeightItem(tmp));
        popupMenu.add(TrackMenuUtils.getDataRangeItem(tmp));
        return popupMenu;
    }

    static public class LoadedInterval {

        Locus range;

        private List<LocusScore> scores;

        int zoom;

        Map<Character, List<LocusScore>> baseScores;

        public LoadedInterval(String chr, int start, int end, int zoom, List<LocusScore> scores, Map<Character, List<LocusScore>> baseScores) {
            range = new Locus(chr, start, end);
            this.zoom = zoom;
            this.scores = scores;
            this.baseScores = baseScores;
        }

        public boolean contains(String chr, int start, int end, int zoom) {
            return this.zoom == zoom && range.contains(chr, start, end);
        }
    }
}


public static List<MediaType> createQualitySourceMediaTypes(Produces mime) {
        if (mime == null || mime.value().length == 0) {
            return WILDCARD_QS_TYPE_SINGLETON_LIST;
        }
        return new ArrayList<MediaType>(createQualitySourceMediaTypes(mime.value()));
    }

package org.glassfish.jersey.message.internal;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * Common media types and functionality.
 *
 * @author Paul Sandoz
 * @author Marek Potociar
 */
public final class MediaTypes {

    /**
     * WADL Media type.
     */
    public static final MediaType WADL_TYPE = MediaType.valueOf("application/vnd.sun.wadl+xml");

    /**
     * A comparator for media types, that implements the "partial order" defined in the resource matching algorithm
     * section of the JAX-RS specification, except that this comparator is "inverted" so that it allows for natural
     * sorting in Java collections, where "lower" values are put to the front of a collection.
     *
     * IOW, when used to sort a collection, the resulting collection can be iterated in a way that the more specific
     * media types are preferred over the less specific ones:
     *
     *

     *   m/n < m/* < */*

     *
     * The actual media type values are ignored, i.e. the different media types are considered equal if they are
     * comparably specific:
     *
     *

     *   compare(m/n, x/y) == 0
     *   compare(m/*, x/*) == 0

     */
    public static final Comparator<MediaType> PARTIAL_ORDER_COMPARATOR = new Comparator<MediaType>() {

        private int rank(final MediaType type) {
            // "m/n" = 0; "m/*" = 1; "*/*" = 3; ...also "*/n" = 2, but that's just nonsense...
            return ((type.isWildcardType() ? 1 : 0) << 1) | (type.isWildcardSubtype() ? 1 : 0);
        }

        @Override
        public int compare(MediaType typeA, MediaType typeB) {
            return rank(typeA) - rank(typeB);
        }
    };

    /**
     * Comparator for lists of media types.
     *

     * The least specific content type of each list is obtained and then compared
     * using {@link #PARTIAL_ORDER_COMPARATOR}.
     *

     * Assumes each list is already ordered according to {@link #PARTIAL_ORDER_COMPARATOR}
     * and therefore the least specific media type is at the end of the list.
     */
    public static final Comparator<List<? extends MediaType>> MEDIA_TYPE_LIST_COMPARATOR = new Comparator<List<? extends MediaType>>() {

        @Override
        public int compare(List<? extends MediaType> o1, List<? extends MediaType> o2) {
            return PARTIAL_ORDER_COMPARATOR.compare(getLeastSpecific(o1), getLeastSpecific(o2));
        }

        private MediaType getLeastSpecific(List<? extends MediaType> l) {
            // FIXME: really comparing just last one?
            return l.get(l.size() - 1);
        }
    };

    /**
     * A singleton list containing the wildcard media type.
     */
    public static final List<MediaType> WILDCARD_TYPE_SINGLETON_LIST = Collections.singletonList(MediaType.WILDCARD_TYPE);

    /**
     * An acceptable media type corresponding to a wildcard type.
     */
    public static final AcceptableMediaType WILDCARD_ACCEPTABLE_TYPE = new AcceptableMediaType("*", "*");

    /**
     * An acceptable media type corresponding to a wildcard type.
     */
    public static final QualitySourceMediaType WILDCARD_QS_TYPE = new QualitySourceMediaType("*", "*");

    /**
     * A singleton list containing the wildcard media type.
     */
    public static final List<MediaType> WILDCARD_QS_TYPE_SINGLETON_LIST = Collections.<MediaType>singletonList(WILDCARD_QS_TYPE);

    /**
     * Cache containing frequently requested media type values with a wildcard subtype.
     */
    private static final Map<String, MediaType> WILDCARD_SUBTYPE_CACHE = new HashMap<String, MediaType>() {

        private static final long serialVersionUID = 3109256773218160485L;

        {
            put("application", new MediaType("application", MediaType.MEDIA_TYPE_WILDCARD));
            put("multipart", new MediaType("multipart", MediaType.MEDIA_TYPE_WILDCARD));
            put("text", new MediaType("text", MediaType.MEDIA_TYPE_WILDCARD));
        }
    };

    /**
     * Predicate for constructing filtering parameter maps that ignore the "q" and "qs" parameters.
     */
    private static final Predicate<String> QUALITY_PARAM_FILTERING_PREDICATE = input -> !Quality.QUALITY_SOURCE_PARAMETER_NAME.equals(input) && !Quality.QUALITY_PARAMETER_NAME.equals(input);

    private MediaTypes() {
        throw new AssertionError("Instantiation not allowed.");
    }

    /**
     * Determine if the two media types are type-equal (their {@link MediaType#getType() type}
     * and {@link MediaType#getSubtype() subtype} are equal). For example:
     *

     *  m/n == m/n
     *  m/n;p1 == m/n;p2
     *
     *  m/n != m/y
     *  m/n != x/n
     *  m/n != x/y

     *
     * @param m1 first media type.
     * @param m2 second media type.
     * @return {@code true} if the two media types are of the same type and subtype,
     * {@code false} otherwise.
     */
    public static boolean typeEqual(MediaType m1, MediaType m2) {
        if (m1 == null || m2 == null) {
            return false;
        }
        return m1.getSubtype().equalsIgnoreCase(m2.getSubtype()) && m1.getType().equalsIgnoreCase(m2.getType());
    }

    /**
     * Determine if the two list of media types share a common
     * {@link #typeEqual(javax.ws.rs.core.MediaType, javax.ws.rs.core.MediaType) type-equal}
     * sub-list.
     *
     * @param ml1 first media type list.
     * @param ml2 second media type list.
     * @return {@code true} if the two media type lists intersect by sharing a
     * common type-equal sub-list, {@code false} otherwise.
     */
    public static boolean intersect(List<? extends MediaType> ml1, List<? extends MediaType> ml2) {
        for (MediaType m1 : ml1) {
            for (MediaType m2 : ml2) {
                if (MediaTypes.typeEqual(m1, m2)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Get the most specific media type from a pair of media types. The most
     * specific media type is the media type from the pair that has least
     * wild cards present, or has more parameters specified.
     *
     * @param m1 the first media type.
     * @param m2 the second media type.
     * @return the most specific media type. If the media types are equally
     * specific then the first media type is returned.
     */
    public static MediaType mostSpecific(MediaType m1, MediaType m2) {
        if (m1.isWildcardType() && !m2.isWildcardType()) {
            return m2;
        }
        if (m1.isWildcardSubtype() && !m2.isWildcardSubtype()) {
            return m2;
        }
        if (m2.getParameters().size() > m1.getParameters().size()) {
            return m2;
        }
        return m1;
    }

    /**
     * Create an unmodifiable list of media types from the values declared in the {@link Consumes}
     * annotation.
     *
     * @param annotation the Consumes annotation.
     * @return the list of {@link MediaType}, ordered according to {@link #PARTIAL_ORDER_COMPARATOR}.
     */
    public static List<MediaType> createFrom(Consumes annotation) {
        if (annotation == null) {
            return WILDCARD_TYPE_SINGLETON_LIST;
        }
        return createFrom(annotation.value());
    }

    /**
     * Create an unmodifiable list of media types from the values declared in the {@link Produces}
     * annotation.
     *
     * @param annotation the Produces annotation.
     * @return the list of {@link MediaType}, ordered according to {@link #PARTIAL_ORDER_COMPARATOR}.
     */
    public static List<MediaType> createFrom(Produces annotation) {
        if (annotation == null) {
            return WILDCARD_TYPE_SINGLETON_LIST;
        }
        return createFrom(annotation.value());
    }

    /**
     * Create an unmodifiable list of media type from a string array of media types.
     *
     * @param mediaTypes the string array of media types.
     * @return the list of {@link MediaType}, ordered according to {@link #PARTIAL_ORDER_COMPARATOR}.
     */
    public static List<MediaType> createFrom(String[] mediaTypes) {
        List<MediaType> result = new ArrayList<MediaType>();
        try {
            for (String mediaType : mediaTypes) {
                HttpHeaderReader.readMediaTypes(result, mediaType);
            }
        } catch (ParseException ex) {
            throw new IllegalArgumentException(ex);
        }
        Collections.sort(result, PARTIAL_ORDER_COMPARATOR);
        return Collections.unmodifiableList(result);
    }

    /**
     * Create a list of quality source media type from the Produces annotation.
     *

     *
     * @param mime the Produces annotation.
     * @return the list of {@link QualitySourceMediaType}, ordered according to
     * {@link org.glassfish.jersey.message.internal.QualitySourceMediaType#COMPARATOR}.
     */

    /**
     * Create a list of quality source media type from an array of media types.
     * <p>
     *
     * @param mediaTypes the array of media types.
     * @return the list of {@link QualitySourceMediaType}, ordered according to
     * the quality source as the primary key and {@link #PARTIAL_ORDER_COMPARATOR}
     * as the secondary key.
     */
    public static List<QualitySourceMediaType> createQualitySourceMediaTypes(String[] mediaTypes) {
        try {
            return HttpHeaderReader.readQualitySourceMediaType(mediaTypes);
        } catch (ParseException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * Reads quality factor from given media type.
     *
     * @param mt media type to read quality parameter from
     * @return quality factor of input media type
     */
    public static int getQuality(MediaType mt) {
        final String qParam = mt.getParameters().get(Quality.QUALITY_PARAMETER_NAME);
        return readQualityFactor(qParam);
    }

    private static int readQualityFactor(final String qParam) throws IllegalArgumentException {
        if (qParam == null) {
            return Quality.DEFAULT;
        } else {
            try {
                return HttpHeaderReader.readQualityFactor(qParam);
            } catch (ParseException ex) {
                throw new IllegalArgumentException(ex);
            }
        }
    }

    /**
     * Strips any quality parameters, i.e. q and qs from given media type.
     *
     * @param mediaType type to strip quality parameters from
     * @return media type instance corresponding to the given one with quality parameters stripped off
     * or the original instance if no such parameters are present
     */
    public static MediaType stripQualityParams(MediaType mediaType) {
        final Map<String, String> oldParameters = mediaType.getParameters();
        if (oldParameters.isEmpty() || (!oldParameters.containsKey(Quality.QUALITY_SOURCE_PARAMETER_NAME) && !oldParameters.containsKey(Quality.QUALITY_PARAMETER_NAME))) {
            return mediaType;
        }
        return new MediaType(mediaType.getType(), mediaType.getSubtype(), oldParameters.entrySet().stream().filter(entry -> QUALITY_PARAM_FILTERING_PREDICATE.test(entry.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
    }

    /**
     * Returns MediaType with wildcard in subtype.
     *
     * @param mediaType original MediaType.
     * @return MediaType with wildcard in subtype.
     */
    public static MediaType getTypeWildCart(MediaType mediaType) {
        MediaType mt = WILDCARD_SUBTYPE_CACHE.get(mediaType.getType());
        if (mt == null) {
            mt = new MediaType(mediaType.getType(), MediaType.MEDIA_TYPE_WILDCARD);
        }
        return mt;
    }

    /**
     * Convert media types into {@link java.lang.String}. The result string contains
     * media types in the same order, separated by comma ',' and enclosed into quotes.
     * For example for input media types
     * {@link MediaType#TEXT_PLAIN_TYPE}, {@link MediaType#TEXT_PLAIN_TYPE} and
     * {@link MediaType#APPLICATION_JSON_TYPE} the result will be
     * "text/plain", "application/json", "text/html".
     *
     * @param mediaTypes {@link Iterable iterable} with {@link MediaType media types}.
     * @return Media types converted into String.
     */
    public static String convertToString(Iterable<MediaType> mediaTypes) {
        StringBuilder sb = new StringBuilder();
        boolean isFirst = true;
        for (MediaType mediaType : mediaTypes) {
            if (!isFirst) {
                sb.append(", ");
            } else {
                isFirst = false;
            }
            sb.append("\"").append(mediaType.toString()).append("\"");
        }
        return sb.toString();
    }

    /**
     * Check if the given media type is a wildcard type.
     *
     * A media type is considered to be a wildcard if either the media type's type or subtype is a wildcard type.
     *
     * @param mediaType media type.
     * @return {@code true} if the media type is a wildcard type, {@code false} otherwise.
     */
    public static boolean isWildcard(final MediaType mediaType) {
        return mediaType.isWildcardType() || mediaType.isWildcardSubtype();
    }
}


try {
                return HttpHeaderReader.readQualityFactor(qParam);
            } catch (ParseException ex) {
                throw new IllegalArgumentException(ex);
            }

package org.glassfish.jersey.message.internal;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * Common media types and functionality.
 *
 * @author Paul Sandoz
 * @author Marek Potociar
 */
public final class MediaTypes {

    /**
     * WADL Media type.
     */
    public static final MediaType WADL_TYPE = MediaType.valueOf("application/vnd.sun.wadl+xml");

    /**
     * A comparator for media types, that implements the "partial order" defined in the resource matching algorithm
     * section of the JAX-RS specification, except that this comparator is "inverted" so that it allows for natural
     * sorting in Java collections, where "lower" values are put to the front of a collection.
     *
     * IOW, when used to sort a collection, the resulting collection can be iterated in a way that the more specific
     * media types are preferred over the less specific ones:
     *
     *

     *   m/n < m/* < */*

     *
     * The actual media type values are ignored, i.e. the different media types are considered equal if they are
     * comparably specific:
     *
     *

     *   compare(m/n, x/y) == 0
     *   compare(m/*, x/*) == 0

     */
    public static final Comparator<MediaType> PARTIAL_ORDER_COMPARATOR = new Comparator<MediaType>() {

        private int rank(final MediaType type) {
            // "m/n" = 0; "m/*" = 1; "*/*" = 3; ...also "*/n" = 2, but that's just nonsense...
            return ((type.isWildcardType() ? 1 : 0) << 1) | (type.isWildcardSubtype() ? 1 : 0);
        }

        @Override
        public int compare(MediaType typeA, MediaType typeB) {
            return rank(typeA) - rank(typeB);
        }
    };

    /**
     * Comparator for lists of media types.
     *

     * The least specific content type of each list is obtained and then compared
     * using {@link #PARTIAL_ORDER_COMPARATOR}.
     *

     * Assumes each list is already ordered according to {@link #PARTIAL_ORDER_COMPARATOR}
     * and therefore the least specific media type is at the end of the list.
     */
    public static final Comparator<List<? extends MediaType>> MEDIA_TYPE_LIST_COMPARATOR = new Comparator<List<? extends MediaType>>() {

        @Override
        public int compare(List<? extends MediaType> o1, List<? extends MediaType> o2) {
            return PARTIAL_ORDER_COMPARATOR.compare(getLeastSpecific(o1), getLeastSpecific(o2));
        }

        private MediaType getLeastSpecific(List<? extends MediaType> l) {
            // FIXME: really comparing just last one?
            return l.get(l.size() - 1);
        }
    };

    /**
     * A singleton list containing the wildcard media type.
     */
    public static final List<MediaType> WILDCARD_TYPE_SINGLETON_LIST = Collections.singletonList(MediaType.WILDCARD_TYPE);

    /**
     * An acceptable media type corresponding to a wildcard type.
     */
    public static final AcceptableMediaType WILDCARD_ACCEPTABLE_TYPE = new AcceptableMediaType("*", "*");

    /**
     * An acceptable media type corresponding to a wildcard type.
     */
    public static final QualitySourceMediaType WILDCARD_QS_TYPE = new QualitySourceMediaType("*", "*");

    /**
     * A singleton list containing the wildcard media type.
     */
    public static final List<MediaType> WILDCARD_QS_TYPE_SINGLETON_LIST = Collections.<MediaType>singletonList(WILDCARD_QS_TYPE);

    /**
     * Cache containing frequently requested media type values with a wildcard subtype.
     */
    private static final Map<String, MediaType> WILDCARD_SUBTYPE_CACHE = new HashMap<String, MediaType>() {

        private static final long serialVersionUID = 3109256773218160485L;

        {
            put("application", new MediaType("application", MediaType.MEDIA_TYPE_WILDCARD));
            put("multipart", new MediaType("multipart", MediaType.MEDIA_TYPE_WILDCARD));
            put("text", new MediaType("text", MediaType.MEDIA_TYPE_WILDCARD));
        }
    };

    /**
     * Predicate for constructing filtering parameter maps that ignore the "q" and "qs" parameters.
     */
    private static final Predicate<String> QUALITY_PARAM_FILTERING_PREDICATE = input -> !Quality.QUALITY_SOURCE_PARAMETER_NAME.equals(input) && !Quality.QUALITY_PARAMETER_NAME.equals(input);

    private MediaTypes() {
        throw new AssertionError("Instantiation not allowed.");
    }

    /**
     * Determine if the two media types are type-equal (their {@link MediaType#getType() type}
     * and {@link MediaType#getSubtype() subtype} are equal). For example:
     *

     *  m/n == m/n
     *  m/n;p1 == m/n;p2
     *
     *  m/n != m/y
     *  m/n != x/n
     *  m/n != x/y

     *
     * @param m1 first media type.
     * @param m2 second media type.
     * @return {@code true} if the two media types are of the same type and subtype,
     * {@code false} otherwise.
     */
    public static boolean typeEqual(MediaType m1, MediaType m2) {
        if (m1 == null || m2 == null) {
            return false;
        }
        return m1.getSubtype().equalsIgnoreCase(m2.getSubtype()) && m1.getType().equalsIgnoreCase(m2.getType());
    }

    /**
     * Determine if the two list of media types share a common
     * {@link #typeEqual(javax.ws.rs.core.MediaType, javax.ws.rs.core.MediaType) type-equal}
     * sub-list.
     *
     * @param ml1 first media type list.
     * @param ml2 second media type list.
     * @return {@code true} if the two media type lists intersect by sharing a
     * common type-equal sub-list, {@code false} otherwise.
     */
    public static boolean intersect(List<? extends MediaType> ml1, List<? extends MediaType> ml2) {
        for (MediaType m1 : ml1) {
            for (MediaType m2 : ml2) {
                if (MediaTypes.typeEqual(m1, m2)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Get the most specific media type from a pair of media types. The most
     * specific media type is the media type from the pair that has least
     * wild cards present, or has more parameters specified.
     *
     * @param m1 the first media type.
     * @param m2 the second media type.
     * @return the most specific media type. If the media types are equally
     * specific then the first media type is returned.
     */
    public static MediaType mostSpecific(MediaType m1, MediaType m2) {
        if (m1.isWildcardType() && !m2.isWildcardType()) {
            return m2;
        }
        if (m1.isWildcardSubtype() && !m2.isWildcardSubtype()) {
            return m2;
        }
        if (m2.getParameters().size() > m1.getParameters().size()) {
            return m2;
        }
        return m1;
    }

    /**
     * Create an unmodifiable list of media types from the values declared in the {@link Consumes}
     * annotation.
     *
     * @param annotation the Consumes annotation.
     * @return the list of {@link MediaType}, ordered according to {@link #PARTIAL_ORDER_COMPARATOR}.
     */
    public static List<MediaType> createFrom(Consumes annotation) {
        if (annotation == null) {
            return WILDCARD_TYPE_SINGLETON_LIST;
        }
        return createFrom(annotation.value());
    }

    /**
     * Create an unmodifiable list of media types from the values declared in the {@link Produces}
     * annotation.
     *
     * @param annotation the Produces annotation.
     * @return the list of {@link MediaType}, ordered according to {@link #PARTIAL_ORDER_COMPARATOR}.
     */
    public static List<MediaType> createFrom(Produces annotation) {
        if (annotation == null) {
            return WILDCARD_TYPE_SINGLETON_LIST;
        }
        return createFrom(annotation.value());
    }

    /**
     * Create an unmodifiable list of media type from a string array of media types.
     *
     * @param mediaTypes the string array of media types.
     * @return the list of {@link MediaType}, ordered according to {@link #PARTIAL_ORDER_COMPARATOR}.
     */
    public static List<MediaType> createFrom(String[] mediaTypes) {
        List<MediaType> result = new ArrayList<MediaType>();
        try {
            for (String mediaType : mediaTypes) {
                HttpHeaderReader.readMediaTypes(result, mediaType);
            }
        } catch (ParseException ex) {
            throw new IllegalArgumentException(ex);
        }
        Collections.sort(result, PARTIAL_ORDER_COMPARATOR);
        return Collections.unmodifiableList(result);
    }

    /**
     * Create a list of quality source media type from the Produces annotation.
     *

     *
     * @param mime the Produces annotation.
     * @return the list of {@link QualitySourceMediaType}, ordered according to
     * {@link org.glassfish.jersey.message.internal.QualitySourceMediaType#COMPARATOR}.
     */
    public static List<MediaType> createQualitySourceMediaTypes(Produces mime) {
        if (mime == null || mime.value().length == 0) {
            return WILDCARD_QS_TYPE_SINGLETON_LIST;
        }
        return new ArrayList<MediaType>(createQualitySourceMediaTypes(mime.value()));
    }

    /**
     * Create a list of quality source media type from an array of media types.
     *

     *
     * @param mediaTypes the array of media types.
     * @return the list of {@link QualitySourceMediaType}, ordered according to
     * the quality source as the primary key and {@link #PARTIAL_ORDER_COMPARATOR}
     * as the secondary key.
     */
    public static List<QualitySourceMediaType> createQualitySourceMediaTypes(String[] mediaTypes) {
        try {
            return HttpHeaderReader.readQualitySourceMediaType(mediaTypes);
        } catch (ParseException ex) {
            throw new IllegalArgumentException(ex);
        }
    }

    /**
     * Reads quality factor from given media type.
     *
     * @param mt media type to read quality parameter from
     * @return quality factor of input media type
     */
    public static int getQuality(MediaType mt) {
        final String qParam = mt.getParameters().get(Quality.QUALITY_PARAMETER_NAME);
        return readQualityFactor(qParam);
    }

    private static int readQualityFactor(final String qParam) throws IllegalArgumentException {
        if (qParam == null) {
            return Quality.DEFAULT;
        } else {

        }
    }

    /**
     * Strips any quality parameters, i.e. q and qs from given media type.
     *
     * @param mediaType type to strip quality parameters from
     * @return media type instance corresponding to the given one with quality parameters stripped off
     * or the original instance if no such parameters are present
     */
    public static MediaType stripQualityParams(MediaType mediaType) {
        final Map<String, String> oldParameters = mediaType.getParameters();
        if (oldParameters.isEmpty() || (!oldParameters.containsKey(Quality.QUALITY_SOURCE_PARAMETER_NAME) && !oldParameters.containsKey(Quality.QUALITY_PARAMETER_NAME))) {
            return mediaType;
        }
        return new MediaType(mediaType.getType(), mediaType.getSubtype(), oldParameters.entrySet().stream().filter(entry -> QUALITY_PARAM_FILTERING_PREDICATE.test(entry.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
    }

    /**
     * Returns MediaType with wildcard in subtype.
     *
     * @param mediaType original MediaType.
     * @return MediaType with wildcard in subtype.
     */
    public static MediaType getTypeWildCart(MediaType mediaType) {
        MediaType mt = WILDCARD_SUBTYPE_CACHE.get(mediaType.getType());
        if (mt == null) {
            mt = new MediaType(mediaType.getType(), MediaType.MEDIA_TYPE_WILDCARD);
        }
        return mt;
    }

    /**
     * Convert media types into {@link java.lang.String}. The result string contains
     * media types in the same order, separated by comma ',' and enclosed into quotes.
     * For example for input media types
     * {@link MediaType#TEXT_PLAIN_TYPE}, {@link MediaType#TEXT_PLAIN_TYPE} and
     * {@link MediaType#APPLICATION_JSON_TYPE} the result will be
     * "text/plain", "application/json", "text/html".
     *
     * @param mediaTypes {@link Iterable iterable} with {@link MediaType media types}.
     * @return Media types converted into String.
     */
    public static String convertToString(Iterable<MediaType> mediaTypes) {
        StringBuilder sb = new StringBuilder();
        boolean isFirst = true;
        for (MediaType mediaType : mediaTypes) {
            if (!isFirst) {
                sb.append(", ");
            } else {
                isFirst = false;
            }
            sb.append("\"").append(mediaType.toString()).append("\"");
        }
        return sb.toString();
    }

    /**
     * Check if the given media type is a wildcard type.
     *
     * A media type is considered to be a wildcard if either the media type's type or subtype is a wildcard type.
     *
     * @param mediaType media type.
     * @return {@code true} if the media type is a wildcard type, {@code false} otherwise.
     */
    public static boolean isWildcard(final MediaType mediaType) {
        return mediaType.isWildcardType() || mediaType.isWildcardSubtype();
    }
}


public String getVersion() {
        return version;
    }

package com.webcohesion.ofx4j.domain.data;

import java.util.List;

/**
 * A message set enclosed in a response envelope.
 *
 * @author Ryan Heaton
 */
public abstract class ResponseMessageSet implements Comparable<ResponseMessageSet> {

    private String version = "1";

    public abstract MessageSetType getType();

    /**
     * The version of this message set.
     *
     * @return The version of this message set.
     */

    /**
     * The version of this message set.
     *
     * @param version The version of this message set.
     */
    public void setVersion(String version) {
        this.version = version;
    }

    /**
     * The list of response messages.
     *
     * @return The list of response messages.
     */
    public abstract List<ResponseMessage> getResponseMessages();

    public int compareTo(ResponseMessageSet o) {
        return getType().compareTo(o.getType());
    }
}


while (repeat-- > 0) {
            int temp = (globalCrcShadow >> 24) ^ inCh;
            globalCrcShadow = (globalCrcShadow << 8) ^ crc32Table[(temp >= 0) ? temp : (temp + 256)];
        }

        this.globalCrc = globalCrcShadow;
    }

    private int globalCrc;
}


public static String help() {
        InputStream is = ArgumentParser.class.getResourceAsStream("helptext.txt");
        String template = Utils.getResource(is);
        return String.format(template, Params.DEFAULT_BUFFER_SIZE, Params.DEFAULT_ENCONDING, Params.DEFAULT_DOWN_PATH, Params.DEFAULT_UP_PATH);
    }

package net.kanstren.tcptunnel;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * Parses command line argument and options.
 *
 * @author Teemu Kanstren.
 */
public class ArgumentParser {

    /**
     * System specific line separator.
     */
    public static String ln = System.getProperty("line.separator");

    /**
     * Parses the given arguments and options.
     *
     * @param args The arguments and options to parse. An option is expected to start with "--".
     * @return The configuration parsed from the arguments/options.
     */
    public static Params parseArgs(String[] args) {
        // in case the program is invoked with no arguments, we print the help
        if (args.length == 0)
            args = new String[] { "--help" };
        Params params = new Params();
        // we collect all errors and if any are found, report them all in the end
        String errors = "";
        // collect all options (starting with "--") here
        List<Option> options = new ArrayList<>();
        // collect all non-option parameters here
        List<String> paramStrs = new ArrayList<>();
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.startsWith("--")) {
                if (arg.equals("--hex")) {
                    // hex is a standalone option, so give it a value and move straight to next one
                    options.add(new Option("--hex", "true"));
                    continue;
                }
                if (arg.equals("--help")) {
                    // help is a standalone option, so give it a value and move straight to next one
                    options.add(new Option("--help", "true"));
                    continue;
                }
                if (arg.equals("--trailing-lf")) {
                    options.add(new Option("--trailing-lf", "true"));
                    continue;
                }
                if (arg.equals("--udp-dns")) {
                    options.add(new Option("--udp-dns", "true"));
                    continue;
                }
                if (arg.equals("--udp-tun")) {
                    options.add(new Option("--udp-tun", "true"));
                    continue;
                }
                if (arg.equals("--gzip")) {
                    options.add(new Option("--gzip", "true"));
                    continue;
                }
                if (args.length <= i + 1) {
                    // all options coming this far should have a value. otherwise it is an error.
                    errors += "No value given for option " + arg + ". Please provide one." + ln;
                    break;
                }
                Option option = new Option(arg, args[i + 1]);
                options.add(option);
                i++;
            } else {
                paramStrs.add(arg);
            }
        }
        // now that we collected all the options, parse the actual content from their values and check they are valid
        errors = parseOptions(options, params, errors);
        // if the options were ok, move to parsing and checking the non-option parameters
        if (params.shouldRun()) {
            boolean paramsOK = true;
            if (paramStrs.size() < 3) {
                errors += "Too few arguments. Need 3, got " + paramStrs.size() + ": " + paramStrs + "." + ln;
                paramsOK = false;
            }
            if (paramStrs.size() > 3) {
                errors += "Too many arguments. Need 3, got " + paramStrs.size() + ": " + paramStrs + "." + ln;
                paramsOK = false;
            }
            if (paramsOK)
                errors = parseParams(paramStrs, params, errors);
        }
        // every error msg ends with a linefeed, so remove the last one
        if (errors.length() > 0)
            errors = errors.substring(0, errors.length() - ln.length());
        params.setErrors(errors);
        return params;
    }

    /**
     * Parse the actual content from the previously captured option definitions.
     *
     * @param options The options from which to parse the content.
     * @param params This is where we store the parsed option content.
     * @param errors The errors previously observed.
     * @return Updated errors with the old one intact and new ones added.
     */
    private static String parseOptions(List<Option> options, Params params, String errors) {
        // we set this to true if the --hex option is given
        boolean hex = false;
        // this is to trace if any loggers are defined. if not, we add the default later.
        int loggers = 0;
        for (Option option : options) {
            String name = option.name;
            switch(name) {
                case "--buffersize":
                    // here we have the buffersize to use for capturing the observed stream and for writing to files etc.
                    try {
                        int bufferSize = Integer.parseInt(option.value);
                        params.setBufferSize(bufferSize);
                        if (bufferSize <= 0)
                            errors += "Buffer size has to be > 0, was: " + bufferSize + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'buffersize':" + option.value + "." + ln;
                    }
                    break;
                case "--encoding":
                    // here we have the character encoding used to decode strings from raw bytes
                    params.setEncoding(option.value);
                    try {
                        boolean supported = Charset.isSupported(option.value);
                        if (!supported)
                            errors += "Unsupported encoding: '" + option.value + "'." + ln;
                    } catch (Exception e) {
                        errors += "Unsupported encoding: '" + option.value + "'." + ln;
                    }
                    break;
                case "--down":
                    // this is the path for storing the downstream traffic (from remote host to local port connection)
                    params.setDownFilePath(option.value);
                    break;
                case "--up":
                    // this is the path for storing the upstream traffic (from local port to remote host)
                    params.setUpFilePath(option.value);
                    break;
                case "--mirror-down-host":
                    params.setMirrorDownHost(option.value);
                    break;
                case "--mirror-down-port":
                    try {
                        int iValue = Integer.parseInt(option.value);
                        params.setMirrorDownPort(iValue);
                        if (iValue <= 1 || iValue > 65535)
                            errors += "Invalid mirror-down port value. Should be between 1-65535, was: " + iValue + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'mirrordownport':" + option.value + "." + ln;
                    }
                    break;
                case "--mirror-up-host":
                    params.setMirrorUpHost(option.value);
                    break;
                case "--mirror-up-port":
                    try {
                        int iValue = Integer.parseInt(option.value);
                        params.setMirrorUpPort(iValue);
                        if (iValue <= 1 || iValue > 65535)
                            errors += "Invalid mirror-up port value. Should be between 1-65535, was: " + iValue + "." + ln;
                    } catch (NumberFormatException e) {
                        errors += "Invalid number for 'mirrorupport':" + option.value + "." + ln;
                    }
                    break;
                case "--hex":
                    // if using byte console logger, this setting changes printing the bytes from integer list to set of hex characters
                    hex = true;
                    break;
                case "--help":
                    // obviously, print out the help
                    System.out.println(help());
                    params.setShouldRun(false);
                    return "";
                case "--logger":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    loggers++;
                    // handle creation in round 2
                    break;
                case "--udp-dns":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    params.setDNS(true);
                    break;
                case "--udp-tun":
                    // increase the number of loggers found so we know later if we need to create the default one or not
                    params.setUDP(true);
                    break;
                case "--trailing-lf":
                    // enable adding a trailing LF to every console print
                    params.setAddLF(true);
                    break;
                case "--gzip":
                    // try to decompress gzip encoding in HTTP requests for string logs
                    params.setGzip(true);
                    break;
                default:
                    // anything not processed above is invalid..
                    errors += "Invalid option '" + name + "'." + ln;
                    break;
            }
        }
        // add the default logger if none found before
        if (loggers == 0)
            options.add(new Option("--logger", "console-string"));
        for (Option option : options) {
            // we create loggers in this separate iteration to have access to affecting parameters (e.g., --hex) and default logger if none specified by user
            String name = option.name;
            switch(name) {
                case "--logger":
                    errors = addLogger(params, option.value, hex, errors);
                    break;
            }
        }
        return errors;
    }

    /**
     * Adds a given type of logger to parser results.
     *
     * @param params For storing parsed parameters (logger instances).
     * @param type The type identifier for the logger.
     * @param hex Whether to print bytes as ints or hex in relevant loggers.
     * @param errors Errors so far.
     * @return Previous and new errors.
     */
    private static String addLogger(Params params, String type, boolean hex, String errors) {
        switch(type) {
            case "console-string":
                // print decoded strings to console
                params.enableStringConsoleLogger();
                break;
            case "console-bytes":
                // print bytes to console, hex or int
                params.enableByteConsoleLogger(hex);
                break;
            case "file-string":
                // write decoded strings to file
                try {
                    params.enableStringFileLogger(params.getDownFilePath(), params.getUpFilePath());
                } catch (IOException e) {
                    errors += "Unable to create string file logger:" + e.getMessage();
                    e.printStackTrace();
                }
                break;
            case "file-bytes":
                // write raw bytes as binary to file
                try {
                    params.enableByteFileLogger(params.getDownFilePath(), params.getUpFilePath());
                } catch (IOException e) {
                    errors += "Unable to create byte file logger:" + e.getMessage();
                }
                break;
            case "mirror-up":
                // write raw bytes as binary to another socket for upstream traffic
                params.enableMirrorUpStreamLogger(params.getMirrorUpHost(), params.getMirrorUpPort());
                break;
            case "mirror-down":
                // write raw bytes as binary to another socket for downstream traffic
                params.enableMirrorDownStreamLogger(params.getMirrorDownHost(), params.getMirrorDownPort());
                break;
            default:
                // anything not processed above is considerd invalid
                errors += "Unknown logger type: '" + type + "'." + ln;
        }
        return errors;
    }

    /**
     * Parse the actual parameters, meaning source (local) port, remote host and remote port.
     * Parameters are the ones not starting with '-'.
     *
     * @param paramStrs The strings for parameters, filtered from options.
     * @param params For storing the parsing results.
     * @param errors Previous parsing errors so far.
     * @return Previous and new parsing errors.
     */
    private static String parseParams(List<String> paramStrs, Params params, String errors) {
        try {
            int sourcePort = Integer.parseInt(paramStrs.get(0));
            params.setSourcePort(sourcePort);
            if (sourcePort < 1 || sourcePort > 65535)
                errors += "Port numbers have to be in range 1-65535, source port was: " + sourcePort + "." + ln;
        } catch (NumberFormatException e) {
            errors += "Unable to parse source port from: '" + paramStrs.get(0) + "'." + ln;
        }
        params.setRemoteHost(paramStrs.get(1));
        try {
            int remotePort = Integer.parseInt(paramStrs.get(2));
            params.setRemotePort(remotePort);
            if (remotePort < 1 || remotePort > 65535)
                errors += "Port numbers have to be in range 1-65535, remote port was: " + remotePort + "." + ln;
        } catch (NumberFormatException e) {
            errors += "Unable to parse remote port from: '" + paramStrs.get(2) + "'." + ln;
        }
        return errors;
    }

    /**
     * @return The help text to show to user.
     */

    /**
     * A class for holding a name-value pair as a configuration option.
     */
    private static class Option {

        /**
         * The option name.
         */
        public final String name;

        /**
         * The unparsed option value.
         */
        public final String value;

        /**
         * @param name The option name.
         * @param value The unparsed option value.
         */
        public Option(String name, String value) {
            this.name = name;
            this.value = value;
        }
    }
}