/**
 * Copyright 2019 Anthony Trinh
 *
 * 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 ch.qos.logback.core.status;

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

abstract public class StatusBase implements Status {

  static private final List<Status> EMPTY_LIST = new ArrayList<Status>(0);

  int level;
  final String message;
  final Object origin;
  List<Status> childrenList;
  Throwable throwable;
  long date;

  StatusBase(int level, String msg, Object origin) {
    this(level, msg, origin, null);
  }

  StatusBase(int level, String msg, Object origin, Throwable t) {
    this.level = level;
    this.message = msg;
    this.origin = origin;
    this.throwable = t;
    this.date = System.currentTimeMillis();
  }

  public synchronized void add(Status child) {
    if (child == null) {
      throw new NullPointerException("Null values are not valid Status.");
    }
    if (childrenList == null) {
      childrenList = new ArrayList<Status>();
    }
    childrenList.add(child);
  }

  public synchronized boolean hasChildren() {
    return ((childrenList != null) && (childrenList.size() > 0));
  }

  public synchronized Iterator<Status> iterator() {
    if (childrenList != null) {
      return childrenList.iterator();
    } else {
      return EMPTY_LIST.iterator();
    }
  }

  public synchronized boolean remove(Status statusToRemove) {
    if (childrenList == null) {
      return false;
    }
    // TODO also search in childrens' children
    return childrenList.remove(statusToRemove);
  }

  public int getLevel() {
    return level;
  }

  // status messages are not supposed to contains cycles.
  // cyclic status arrangements are like to cause deadlocks
  // when this method is called from different thread on
  // different status objects lying on the same cycle
  public synchronized int getEffectiveLevel() {
    int result = level;
    int effLevel;

    Iterator it = iterator();
    Status s;
    while (it.hasNext()) {
      s = (Status) it.next();
      effLevel = s.getEffectiveLevel();
      if (effLevel > result) {
        result = effLevel;
      }
    }
    return result;
  }

  public String getMessage() {
    return message;
  }

  public Object getOrigin() {
    return origin;
  }

  public Throwable getThrowable() {
    return throwable;
  }

  public Long getDate() {
    return date;
  }

  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder();
    switch (getEffectiveLevel()) {
    case INFO:
      buf.append("INFO");
      break;
    case WARN:
      buf.append("WARN");
      break;
    case ERROR:
      buf.append("ERROR");
      break;
    }
    if (origin != null) {
      buf.append(" in ");
      buf.append(origin);
      buf.append(" -");
    }

    buf.append(" ");
    buf.append(message);

    if (throwable != null) {
      buf.append(" ");
      buf.append(throwable);
    }

    return buf.toString();
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + level;
    result = prime * result + ((message == null) ? 0 : message.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final StatusBase other = (StatusBase) obj;
    if (level != other.level)
      return false;
    if (message == null) {
      if (other.message != null)
        return false;
    } else if (!message.equals(other.message))
      return false;
    return true;
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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.
 */
// Contributors:  Georg Lundesgaard
package ch.qos.logback.core.joran.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;

import ch.qos.logback.core.joran.spi.DefaultClass;
import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.AggregationType;
import ch.qos.logback.core.util.PropertySetterException;

/**
 * General purpose Object property setter. Clients repeatedly invokes
 * {@link #setProperty setProperty(name,value)} in order to invoke setters on
 * the Object specified in the constructor.
 *
 *

 * Usage:
 *
 *

 * PropertySetter ps = new PropertySetter(anObject);
 * ps.set("name", "Joe");
 * ps.set("age", "32");
 * ps.set("isMale", "true");
 *

 *
 * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), and
 * setMale(true) if such methods exist with those signatures. Otherwise an
 * {@link IntrospectionException} are thrown.
 *
 * @author Anders Kristensen
 * @author Ceki Gulcu
 */
public class PropertySetter extends ContextAwareBase {

  protected Object obj;
  protected Class<?> objClass;
  protected PropertyDescriptor[] propertyDescriptors;
  protected MethodDescriptor[] methodDescriptors;

  /**
   * Create a new PropertySetter for the specified Object. This is done in
   * preparation for invoking {@link #setProperty} one or more times.
   *
   * @param obj
   *          the object for which to set properties
   */
  public PropertySetter(Object obj) {
    this.obj = obj;
    this.objClass = obj.getClass();
  }

  /**
   * Uses JavaBeans {@link Introspector} to computer setters of object to be
   * configured.
   */
  protected void introspect() {
    try {
      propertyDescriptors = Introspector.getPropertyDescriptors(this.objClass);
      methodDescriptors = Introspector.getMethodDescriptors(this.objClass);
    } catch (IntrospectionException ex) {
      addError("Failed to introspect " + obj + ": " + ex.getMessage());
      propertyDescriptors = new PropertyDescriptor[0];
      methodDescriptors = new MethodDescriptor[0];
    }
  }

  /**
   * Set a property on this PropertySetter's Object. If successful, this method
   * will invoke a setter method on the underlying Object. The setter is the one
   * for the specified property name and the value is determined partly from the
   * setter argument type and partly from the value specified in the call to
   * this method.
   *
   *

   * If the setter expects a String no conversion is necessary. If it expects an
   * int, then an attempt is made to convert 'value' to an int using new
   * Integer(value). If the setter expects a boolean, the conversion is by new
   * Boolean(value).
   *
   * @param name
   *          name of the property
   * @param value
   *          String value of the property
   */
  public void setProperty(String name, String value) {
    if (value == null) {
      return;
    }

    name = Introspector.decapitalize(name);

    PropertyDescriptor prop = getPropertyDescriptor(name);

    if (prop == null) {
      addWarn("No such property [" + name + "] in " + objClass.getName() + ".");
    } else {
      try {
        setProperty(prop, name, value);
      } catch (PropertySetterException ex) {
        addWarn("Failed to set property [" + name + "] to value \"" + value
            + "\". ", ex);
      }
    }
  }

  /**
   * Set the named property given a {@link PropertyDescriptor}.
   *
   * @param prop
   *          A PropertyDescriptor describing the characteristics of the
   *          property to set.
   * @param name
   *          The named of the property to set.
   * @param value
   *          The value of the property.
   * @throws PropertySetterException
   *          No setter for property; setter's argument list is not equal to 1; parameter conversion failure
   */
  public void setProperty(PropertyDescriptor prop, String name, String value)
      throws PropertySetterException {
    Method setter = prop.getWriteMethod();

    if (setter == null) {
      throw new PropertySetterException("No setter for property [" + name
          + "].");
    }

    Class<?>[] paramTypes = setter.getParameterTypes();

    if (paramTypes.length != 1) {
      throw new PropertySetterException("#params for setter != 1");
    }

    Object arg;

    try {
      arg = StringToObjectConverter.convertArg(this, value, paramTypes[0]);
    } catch (Throwable t) {
      throw new PropertySetterException("Conversion to type [" + paramTypes[0]
          + "] failed. ", t);
    }

    if (arg == null) {
      throw new PropertySetterException("Conversion to type [" + paramTypes[0]
          + "] failed.");
    }
    try {
      setter.invoke(obj, arg);
    } catch (Exception ex) {
      throw new PropertySetterException(ex);
    }
  }

  public AggregationType computeAggregationType(String name) {
    // findAdderMethod() capitalizes name's 1st letter before search
    Method addMethod = findAdderMethod(name);

    // if the
    if (addMethod != null) {
      AggregationType type = computeRawAggregationType(addMethod);
      switch (type) {
      case NOT_FOUND:
        return AggregationType.NOT_FOUND;
      case AS_BASIC_PROPERTY:
        return AggregationType.AS_BASIC_PROPERTY_COLLECTION;
      case AS_COMPLEX_PROPERTY:
        return AggregationType.AS_COMPLEX_PROPERTY_COLLECTION;
      case AS_BASIC_PROPERTY_COLLECTION:
      case AS_COMPLEX_PROPERTY_COLLECTION:
        addError("Unexpected AggregationType " + type);
        break;
      }
    }

    Method setterMethod = findSetterMethod(name);
    if (setterMethod != null) {
      return computeRawAggregationType(setterMethod);
    } else {
      // we have failed
      return AggregationType.NOT_FOUND;
    }
  }

  private Method findAdderMethod(String name) {
    name = capitalizeFirstLetter(name);
    return getMethod("add" + name);
  }

  private Method findSetterMethod(String name) {
    String dName = Introspector.decapitalize(name);
    PropertyDescriptor propertyDescriptor = getPropertyDescriptor(dName);
    if (propertyDescriptor != null) {
      return propertyDescriptor.getWriteMethod();
    } else {
      return null;
    }
  }

  private Class<?> getParameterClassForMethod(Method method) {
    if (method == null) {
      return null;
    }
    Class<?>[] classArray = method.getParameterTypes();
    if (classArray.length != 1) {
      return null;
    } else {
      return classArray[0];
    }
  }

  private AggregationType computeRawAggregationType(Method method) {
    Class<?> parameterClass = getParameterClassForMethod(method);
    if (parameterClass == null) {
      return AggregationType.NOT_FOUND;
    }
    if (StringToObjectConverter.canBeBuiltFromSimpleString(parameterClass)) {
      return AggregationType.AS_BASIC_PROPERTY;
    } else {
      return AggregationType.AS_COMPLEX_PROPERTY;
    }
  }

  /**
   * Can the given clazz instantiable with certainty?
   *
   * @param clazz
   *          The class to test for instantiability
   * @return true if clazz can be instantiated, and false otherwise.
   */
  private boolean isUnequivocallyInstantiable(Class<?> clazz) {
    if (clazz.isInterface()) {
      return false;
    }
    // checking for constructors would be more elegant, but in
    // classes without any declared constructors, Class.getConstructor()
    // returns null.
    Object o;
    try {
      o = clazz.getDeclaredConstructor().newInstance();
      if (o != null) {
        return true;
      } else {
        return false;
      }
    } catch (InstantiationException e) {
      return false;
    } catch (IllegalAccessException e) {
      return false;
    } catch (NoSuchMethodException e) {
      return false;
    } catch (InvocationTargetException e) {
      return false;
    }
  }

  public Class<?> getObjClass() {
    return objClass;
  }

  public void addComplexProperty(String name, Object complexProperty) {
    Method adderMethod = findAdderMethod(name);
    // first let us use the addXXX method
    if (adderMethod != null) {
      Class<?>[] paramTypes = adderMethod.getParameterTypes();
      if (!isSanityCheckSuccessful(name, adderMethod, paramTypes,
          complexProperty)) {
        return;
      }
      invokeMethodWithSingleParameterOnThisObject(adderMethod, complexProperty);
    } else {
      addError("Could not find method [" + "add" + name + "] in class ["
          + objClass.getName() + "].");
    }
  }

  void invokeMethodWithSingleParameterOnThisObject(Method method,
      Object parameter) {
    Class<?> ccc = parameter.getClass();
    try {
      method.invoke(this.obj, parameter);
    } catch (Exception e) {
      addError("Could not invoke method " + method.getName() + " in class "
          + obj.getClass().getName() + " with parameter of type "
          + ccc.getName(), e);
    }
  }

  public void addBasicProperty(String name, String strValue) {

    if (strValue == null) {
      return;
    }

    name = capitalizeFirstLetter(name);
    Method adderMethod = findAdderMethod(name);

    if (adderMethod == null) {
      addError("No adder for property [" + name + "].");
      return;
    }

    Class<?>[] paramTypes = adderMethod.getParameterTypes();
    isSanityCheckSuccessful(name, adderMethod, paramTypes, strValue);

    Object arg;
    try {
      arg = StringToObjectConverter.convertArg(this, strValue, paramTypes[0]);
    } catch (Throwable t) {
      addError("Conversion to type [" + paramTypes[0] + "] failed. ", t);
      return;
    }
    if (arg != null) {
      invokeMethodWithSingleParameterOnThisObject(adderMethod, strValue);
    }
  }

  public void setComplexProperty(String name, Object complexProperty) {
    String dName = Introspector.decapitalize(name);
    PropertyDescriptor propertyDescriptor = getPropertyDescriptor(dName);

    if (propertyDescriptor == null) {
      addWarn("Could not find PropertyDescriptor for [" + name + "] in "
          + objClass.getName());

      return;
    }

    Method setter = propertyDescriptor.getWriteMethod();

    if (setter == null) {
      addWarn("Not setter method for property [" + name + "] in "
          + obj.getClass().getName());

      return;
    }

    Class<?>[] paramTypes = setter.getParameterTypes();

    if (!isSanityCheckSuccessful(name, setter, paramTypes, complexProperty)) {
      return;
    }
    try {
      invokeMethodWithSingleParameterOnThisObject(setter, complexProperty);

    } catch (Exception e) {
      addError("Could not set component " + obj + " for parent component "
          + obj, e);
    }
  }

  private boolean isSanityCheckSuccessful(String name, Method method,
      Class<?>[] params, Object complexProperty) {
    Class<?> ccc = complexProperty.getClass();
    if (params.length != 1) {
      addError("Wrong number of parameters in setter method for property ["
          + name + "] in " + obj.getClass().getName());

      return false;
    }

    if (!params[0].isAssignableFrom(complexProperty.getClass())) {
      addError("A \"" + ccc.getName() + "\" object is not assignable to a \""
          + params[0].getName() + "\" variable.");
      addError("The class \"" + params[0].getName() + "\" was loaded by ");
      addError("[" + params[0].getClassLoader() + "] whereas object of type ");
      addError("\"" + ccc.getName() + "\" was loaded by ["
          + ccc.getClassLoader() + "].");
      return false;
    }

    return true;
  }

  private String capitalizeFirstLetter(String name) {
    return name.substring(0, 1).toUpperCase(Locale.US) + name.substring(1);
  }

  protected Method getMethod(String methodName) {
    if (methodDescriptors == null) {
      introspect();
    }

    for (int i = 0; i < methodDescriptors.length; i++) {
      if (methodName.equals(methodDescriptors[i].getName())) {
        return methodDescriptors[i].getMethod();
      }
    }

    return null;
  }

  protected PropertyDescriptor getPropertyDescriptor(String name) {
    if (propertyDescriptors == null) {
      introspect();
    }

    for (int i = 0; i < propertyDescriptors.length; i++) {
      // System.out.println("Comparing " + name + " against "
      // + propertyDescriptors[i].getName());
      if (name.equals(propertyDescriptors[i].getName())) {
        // System.out.println("matched");
        return propertyDescriptors[i];
      }
    }

    return null;
  }

  public Object getObj() {
    return obj;
  }

  Method getRelevantMethod(String name, AggregationType aggregationType) {
    String cName = capitalizeFirstLetter(name);
    Method relevantMethod;
    if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY_COLLECTION) {
      relevantMethod = findAdderMethod(cName);
    } else if (aggregationType == AggregationType.AS_COMPLEX_PROPERTY) {
      relevantMethod = findSetterMethod(cName);
    } else {
      throw new IllegalStateException(aggregationType + " not allowed here");
    }
    return relevantMethod;
  }

  <T extends Annotation> T getAnnotation(String name, Class<T> annonationClass,
      Method relevantMethod) {

    if (relevantMethod != null) {
      return relevantMethod.getAnnotation(annonationClass);
    } else {
      return null;
    }
  }

  Class<?> getDefaultClassNameByAnnonation(String name, Method relevantMethod) {
    DefaultClass defaultClassAnnon = getAnnotation(name, DefaultClass.class,
        relevantMethod);
    if (defaultClassAnnon != null) {
      return defaultClassAnnon.value();
    }
    return null;
  }

  Class<?> getByConcreteType(String name, Method relevantMethod) {

    Class<?> paramType = getParameterClassForMethod(relevantMethod);
    if (paramType == null) {
      return null;
    }

    boolean isUnequivocallyInstantiable = isUnequivocallyInstantiable(paramType);
    if (isUnequivocallyInstantiable) {
      return paramType;
    } else {
      return null;
    }

  }

  public Class<?> getClassNameViaImplicitRules(String name,
      AggregationType aggregationType, DefaultNestedComponentRegistry registry) {

    Class<?> registryResult = registry.findDefaultComponentType(obj.getClass(),
        name);
    if (registryResult != null) {
      return registryResult;
    }
    // find the relevant method for the given property name and aggregationType
    Method relevantMethod = getRelevantMethod(name, aggregationType);
    if (relevantMethod == null) {
      return null;
    }
    Class<?> byAnnotation = getDefaultClassNameByAnnonation(name, relevantMethod);
    if (byAnnotation != null) {
      return byAnnotation;
    }
    return getByConcreteType(name, relevantMethod);
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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 ch.qos.logback.core.property;

import ch.qos.logback.core.PropertyDefinerBase;
import ch.qos.logback.core.util.Loader;
import ch.qos.logback.core.util.OptionHelper;

import java.net.URL;

/**
 * In conjunction with {@link ch.qos.logback.core.joran.action.PropertyAction} sets
 * the named variable to "true" if the {@link #setResource(String) resource} specified
 * by the user is available on the class path, "false" otherwise.
 *
 * @see #getPropertyValue()
 *
 * @author XuHuisheng
 * @author Ceki Gulcu
 * @since 1.1.0
 */
public class ResourceExistsPropertyDefiner extends PropertyDefinerBase {

  String resourceStr;

  public String getResource() {
    return resourceStr;
  }

  /**
   * Sets the resource to search for on the class path.
   *
   * @param resource the resource path
   */
  public void setResource(String resource) {
    this.resourceStr = resource;
  }

  /**
   * Returns the string "true" if the {@link #setResource(String) resource} specified by the
   * user is available on the class path, "false" otherwise.
   *
   * @return "true"|"false" depending on the availability of resource on the classpath
   */
  public String getPropertyValue() {
    if (OptionHelper.isEmpty(resourceStr)) {
      addError("The \"resource\" property must be set.");
      return null;
    }

    URL resourceURL = Loader.getResourceBySelfClassLoader(resourceStr);
    return booleanAsStr(resourceURL != null);
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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 ch.qos.logback.classic.util;

import java.util.HashMap;

/**
 * This class extends InheritableThreadLocal so that children threads get a copy
 * of the parent's hashmap.
 *
 * @author Ceki Gülcü
 */
public class CopyOnInheritThreadLocal extends
    InheritableThreadLocal<HashMap<String, String>> {

  /**
   * Child threads should get a copy of the parent's hashmap.
   */
  @Override
  protected HashMap<String, String> childValue(
      HashMap<String, String> parentValue) {
    if (parentValue == null) {
      return null;
    } else {
      return new HashMap<String, String>(parentValue);
    }
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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 ch.qos.logback.core.subst;

import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.spi.ScanException;

import java.util.List;

// E = TE|T
//   = T(E|~)
// E = TEopt where Eopt = E|~
// T = LITERAL | { C } |'${' V '}'
// C = E|E :- E
//   = E(':-'E|~)
// V = E|E :- E
//   = E(':-'E|~)

/**
 * Parse a token list returning a node chain.
 *
 * @author Ceki Gulcu
 */
public class Parser {

  final List<Token> tokenList;
  int pointer = 0;

  public Parser(List<Token> tokenList) {
    this.tokenList = tokenList;
  }

  public Node parse() throws ScanException {
    if (tokenList == null || tokenList.isEmpty())
      return null;
    return E();
  }

  private Node E() throws ScanException {
    Node t = T();
    if (t == null) {
      return null;
    }
    Node eOpt = Eopt();
    if (eOpt != null) {
      t.append(eOpt);
    }
    return t;
  }

  // Eopt = E|~
  private Node Eopt() throws ScanException {
    Token next = peekAtCurentToken();
    if (next == null) {
      return null;
    } else {
      return E();
    }
  }

  // T = LITERAL | '${' V '}'
  private Node T() throws ScanException {
    Token t = peekAtCurentToken();

    switch (t.type) {
      case LITERAL:
        advanceTokenPointer();
        return makeNewLiteralNode(t.payload);
      case CURLY_LEFT:
        advanceTokenPointer();
        Node innerNode = C();
        Token right = peekAtCurentToken();
        expectCurlyRight(right);
        advanceTokenPointer();
        Node curlyLeft = makeNewLiteralNode(CoreConstants.LEFT_ACCOLADE);
        curlyLeft.append(innerNode);
        curlyLeft.append(makeNewLiteralNode(CoreConstants.RIGHT_ACCOLADE));
        return curlyLeft;
      case START:
        advanceTokenPointer();
        Node v = V();
        Token w = peekAtCurentToken();
        expectCurlyRight(w);
        advanceTokenPointer();
        return v;
      default:
        return null;
    }
  }

  private Node makeNewLiteralNode(String s) {
    return new Node(Node.Type.LITERAL, s);
  }

  // V = E(':='E|~)
  private Node V() throws ScanException {
    Node e = E();
    Node variable = new Node(Node.Type.VARIABLE, e);
    Token t = peekAtCurentToken();
    if (isDefaultToken(t)) {
      advanceTokenPointer();
      Node def = E();
      variable.defaultPart = def;
    }
    return variable;
  }

  // C = E(':='E|~)
  private Node C() throws ScanException {
    Node e0 = E();
    Token t = peekAtCurentToken();
    if (isDefaultToken(t)) {
      advanceTokenPointer();
      Node literal = makeNewLiteralNode(CoreConstants.DEFAULT_VALUE_SEPARATOR);
      e0.append(literal);
      Node e1 = E();
      e0.append(e1);
    }
    return e0;
  }

  private boolean isDefaultToken(Token t) {
    return t != null && t.type == Token.Type.DEFAULT;
  }

  void advanceTokenPointer() {
    pointer++;
  }

  void expectNotNull(Token t, String expected) {
    if (t == null) {
      throw new IllegalArgumentException("All tokens consumed but was expecting \""
              + expected + "\"");
    }
  }

  void expectCurlyRight(Token t) throws ScanException {
    expectNotNull(t, "}");
    if (t.type != Token.Type.CURLY_RIGHT) {
      throw new ScanException("Expecting }");
    }
  }

  Token peekAtCurentToken() {
    if (pointer < tokenList.size()) {
      return tokenList.get(pointer);
    }
    return null;
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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 ch.qos.logback.core.joran.action;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;

import ch.qos.logback.core.joran.event.SaxEvent;
import ch.qos.logback.core.joran.event.SaxEventRecorder;
import ch.qos.logback.core.joran.spi.InterpretationContext;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;

public class IncludeAction extends AbstractIncludeAction {

  private static final String INCLUDED_TAG = "included";
  private static final String CONFIG_TAG = "configuration";
  private int eventOffset = 2;

  /**
   * Sets the list index into the event player's list, where the events
   * from the processed include will be inserted
   * @param offset the array index
   */
  protected void setEventOffset(int offset) {
    this.eventOffset = offset;
  }

  /**
   * Creates a SAX event recorder based on given parameters. Subclasses
   * should override this as necessary.
   * @param in input stream
   * @param url URL to opened file/resource
   * @return the newly created recorder
   */
  protected SaxEventRecorder createRecorder(InputStream in, URL url) {
    return new SaxEventRecorder(getContext());
  }

  /**
   * Processes an include
   * @param ic context
   * @param url URL to file/resource being included
   */
  @Override
  protected void processInclude(InterpretationContext ic, URL url) throws JoranException {

    InputStream in = openURL(url);

    try {
      if (in != null) {
        // add URL to watch list in case the "scan" flag is true, in
        // which case this URL is periodically checked for changes
        ConfigurationWatchListUtil.addToWatchList(getContext(), url);

        // parse the include
        SaxEventRecorder recorder = createRecorder(in, url);
        recorder.setContext(getContext());
        recorder.recordEvents(in);

        // remove the leading/trailing tags (<included> or <configuration>)
        trimHeadAndTail(recorder);

        ic.getJoranInterpreter().getEventPlayer().addEventsDynamically(recorder.getSaxEventList(), this.eventOffset);
      }
    } catch (JoranException e) {
      optionalWarning("Failed processing [" + url.toString() + "]", e);
    } finally {
      close(in);
    }
  }

  /**
   * Opens the given URL, logging any exceptions
   * @param url URL of file/resource to open
   * @return an input stream to the URL; or {@code null} if the URL could not be opened
   */
  private InputStream openURL(URL url) {
    try {
      return url.openStream();
    } catch (IOException e) {
      optionalWarning("Failed to open [" + url.toString() + "]", e);
      return null;
    }
  }

  /**
   * Removes the head tag and tail tag if they are named either
   * "included" or "configuration"
   * @param recorder the SAX Event recorder containing the tags
   */
  private void trimHeadAndTail(SaxEventRecorder recorder) {
    List<SaxEvent> saxEventList = recorder.getSaxEventList();
    if (saxEventList.size() == 0) {
      return;
    }

    boolean includedTagFound = false;
    boolean configTagFound = false;

    // find opening element
    SaxEvent first = saxEventList.get(0);
    if (first != null) {
      String elemName = getEventName(first);
      includedTagFound = INCLUDED_TAG.equalsIgnoreCase(elemName);
      configTagFound = CONFIG_TAG.equalsIgnoreCase(elemName);
    }

    // if opening element found, remove it, and then remove the closing element
    if (includedTagFound || configTagFound) {
      saxEventList.remove(0);

      final int listSize = saxEventList.size();
      if (listSize == 0) {
        return;
      }

      final int lastIndex = listSize - 1;
      SaxEvent last = saxEventList.get(lastIndex);

      if (last != null) {
        String elemName = getEventName(last);
        if ((includedTagFound && INCLUDED_TAG.equalsIgnoreCase(elemName)) ||
          (configTagFound && CONFIG_TAG.equalsIgnoreCase(elemName))) {

          saxEventList.remove(lastIndex);
        }
      }
    }
  }

  /**
   * Gets the event name of a {@code SaxEvent}
   * @param event SaxEvent to evaluate
   * @return {@code event.qName} is if it's not empty; otherwise, {@code event.localName}
   */
  private String getEventName(SaxEvent event) {
    return event.qName.length() > 0 ? event.qName : event.localName;
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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 ch.qos.logback.classic.net.server;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.concurrent.Executor;

import javax.net.ServerSocketFactory;

import ch.qos.logback.classic.net.ReceiverBase;
import ch.qos.logback.core.net.AbstractSocketAppender;
import ch.qos.logback.core.net.server.ServerListener;
import ch.qos.logback.core.net.server.ServerRunner;
import ch.qos.logback.core.util.CloseUtil;

/**
 * A logging socket server that is configurable using Joran.
 *
 * @author Carl Harris
 */
public class ServerSocketReceiver extends ReceiverBase {

  /**
   * Default {@link ServerSocket} backlog
   */
  public static final int DEFAULT_BACKLOG = 50;

  private int port = AbstractSocketAppender.DEFAULT_PORT;
  private int backlog = DEFAULT_BACKLOG;
  private String address;

  private ServerRunner runner;

  /**
   * Starts the server.
   */
  protected boolean shouldStart() {
    ServerSocket serverSocket = null;
    try {
      serverSocket = getServerSocketFactory().createServerSocket(
          getPort(), getBacklog(), getInetAddress());

      ServerListener<RemoteAppenderClient> listener =
          createServerListener(serverSocket);

      runner = createServerRunner(listener, getContext().getScheduledExecutorService());
      runner.setContext(getContext());
      return true;
    }
    catch (Exception ex) {
      addError("server startup error: " + ex, ex);
      CloseUtil.closeQuietly(serverSocket);
      return false;
    }
  }

  protected ServerListener<RemoteAppenderClient> createServerListener(
      ServerSocket socket) {
    return new RemoteAppenderServerListener(socket);
  }

  protected ServerRunner createServerRunner(
      ServerListener<RemoteAppenderClient> listener,
      Executor executor) {
    return new RemoteAppenderServerRunner(listener, executor);
  }

  @Override
  protected Runnable getRunnableTask() {
    return runner;
  }

  /**
   * {@inheritDoc}
   */
  protected void onStop() {
    try {
      if (runner == null) return;
      runner.stop();
    }
    catch (IOException ex) {
      addError("server shutdown error: " + ex, ex);
    }
  }

  /**
   * Gets the server socket factory.
   *

   * Subclasses may override to provide a custom factory.
   * @return server socket factory
   * @throws Exception
   */
  protected ServerSocketFactory getServerSocketFactory() throws Exception {
    return ServerSocketFactory.getDefault();
  }

  /**
   * Gets the local address for the listener.
   * @return an {@link InetAddress} representation of the local address.
   * @throws UnknownHostException
   */
  protected InetAddress getInetAddress() throws UnknownHostException {
    if (getAddress() == null) return null;
    return InetAddress.getByName(getAddress());
  }

  /**
   * Gets the local port for the listener.
   * @return local port
   */
  public int getPort() {
    return port;
  }

  /**
   * Sets the local port for the listener.
   * @param port the local port to set
   */
  public void setPort(int port) {
    this.port = port;
  }

  /**
   * Gets the listener queue depth.
   *

   * This represents the number of connected clients whose connections
   * have not yet been accepted.
   * @return queue depth
   * @see java.net.ServerSocket
   */
  public int getBacklog() {
    return backlog;
  }

  /**
   * Sets the listener queue depth.
   *

   * This represents the number of connected clients whose connections
   * have not yet been accepted.
   * @param backlog the queue depth to set
   * @see java.net.ServerSocket
   */
  public void setBacklog(int backlog) {
    this.backlog = backlog;
  }

  /**
   * Gets the local address for the listener.
   * @return a string representation of the local address
   */
  public String getAddress() {
    return address;
  }

  /**
   * Sets the local address for the listener.
   * @param address a host name or a string representation of an IP address
   */
  public void setAddress(String address) {
    this.address = address;
  }

}


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

package com.algolia.search.saas;

import java.io.IOException;

/**
 * Any error that was encountered during the processing of a request.
 * Could be server-side, network failure, or client-side.
 */
public class AlgoliaException extends Exception {
    /** HTTP status code. Only valid when the error originates from the server. */
    private int statusCode;

    public AlgoliaException(String message) {
        super(message);
    }

    public AlgoliaException(String message, Throwable throwable)
    {
        super(message, throwable);
    }

    public AlgoliaException(String message, int statusCode)
    {
        super(message);
        this.statusCode = statusCode;
    }

    private static final long serialVersionUID = 1L;

    /**
     * Get the error's HTTP status code (if any).
     * Only valid when the exception is an application-level error. Values are documented in the
     * REST API.
     *
     * @return The HTTP status code, or 0 if not available.
     */
    public int getStatusCode()
    {
        return statusCode;
    }

    /**
     * Test whether this error is transient.
     *
     * @return true if transient, false if fatal.
     */
    public boolean isTransient() {
        Throwable cause = getCause();
        if (cause == null) {
            return isServerError(statusCode);
        } else if (cause instanceof AlgoliaException) {
            return ((AlgoliaException)cause).isTransient();
        } else if (cause instanceof IOException) {
            return true;
        } else {
            return false;
        }
    }

    private static boolean isServerError(int statusCode) {
        return statusCode / 100 == 5;
    }
}


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

package com.algolia.search.saas.places;

import android.support.annotation.NonNull;

import com.algolia.search.saas.AbstractClient;
import com.algolia.search.saas.AlgoliaException;
import com.algolia.search.saas.CompletionHandler;
import com.algolia.search.saas.Request;

import org.json.JSONException;
import org.json.JSONObject;

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

/**
 * Client for [Algolia Places](https://community.algolia.com/places/).
 */
public class PlacesClient extends AbstractClient {
    // ----------------------------------------------------------------------
    // Initialization
    // ----------------------------------------------------------------------

    /**
     * Create a new authenticated Algolia Places client.
     *
     * @param applicationID The application ID (available in your Algolia Dashboard).
     * @param apiKey A valid API key for the service.
     */
    public PlacesClient(@NonNull String applicationID, @NonNull String apiKey) {
        super(applicationID, apiKey, null, null);
        setDefaultHosts();
    }

    /**
     * Create a new unauthenticated Algolia Places client.
     *
     * NOTE: The rate limit for the unauthenticated API is significantly lower than for the authenticated API.
     */
    public PlacesClient() {
        super(null, null, null, null);
        setDefaultHosts();
    }

    /**
     * Set the default hosts for Algolia Places.
     */
    private void setDefaultHosts() {
        List<String> fallbackHosts = Arrays.asList(
                "places-1.algolianet.com",
                "places-2.algolianet.com",
                "places-3.algolianet.com"
        );
        Collections.shuffle(fallbackHosts);

        List<String> hosts = new ArrayList<>(fallbackHosts.size() + 1);
        hosts.add("places-dsn.algolia.net");
        hosts.addAll(fallbackHosts);
        String[] hostsArray = hosts.toArray(new String[hosts.size()]);

        setReadHosts(hostsArray);
        setWriteHosts(hostsArray);
    }

    // ----------------------------------------------------------------------
    // Public operations
    // ----------------------------------------------------------------------

    /**
     * Search for places.
     *
     * @param params Search parameters.
     * @param completionHandler The listener that will be notified of the request's outcome.
     * @return A cancellable request.
     */
    public Request searchAsync(@NonNull PlacesQuery params, @NonNull CompletionHandler completionHandler) {
        final PlacesQuery paramsCopy = new PlacesQuery(params);
        return new AsyncTaskRequest(completionHandler) {
            @Override
            protected @NonNull JSONObject run() throws AlgoliaException {
                return search(paramsCopy);
            }
        }.start();
    }

    // ----------------------------------------------------------------------
    // Internal operations
    // ----------------------------------------------------------------------

    /**
     * Search for places.
     *
     * @param params Search parameters.
     */
    protected JSONObject search(@NonNull PlacesQuery params) throws AlgoliaException {
        try {
            JSONObject body = new JSONObject()
                .put("params", params.build());
            return postRequest("/1/places/query", /* urlParameters: */ null, body.toString(), true /* readOperation */, /* requestOptions: */ null);
        }
        catch (JSONException e) {
            throw new RuntimeException(e); // should never happen
        }
    }

    /**
     * Get a place by its objectID.
     * @param objectID the record's identifier.
     * @return the corresponding record.
     * @throws AlgoliaException when the given objectID does not exist.
     */
    public JSONObject getByObjectID(@NonNull String objectID) throws AlgoliaException {
        return getRequest("/1/places/" + objectID, /* urlParameters: */ null, false, /* requestOptions: */ null)    ;
    }
}