package gedi.solutions.geode.rest.app;

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.google.gson.Gson;

import gedi.solutions.geode.io.QuerierService;
import gedi.solutions.geode.rest.app.exception.DataError;
import gedi.solutions.geode.rest.app.exception.FaultAgent;

/**
 * Generic READ/WRITE GemFire REST service
 * @author Gregory Green
 *
 */
@RestController
@RequestMapping("/query")
public class GeodeQueryServiceRestService
{

	@Autowired
	Gson gson;

	@Autowired
	QuerierService querierService;

	@Autowired
	FaultAgent faultAgent;

	/**
	 * Execute a GemFire query with no limit
	 * @param query the OQL to execute
	 * @return the JSON array records
	 * @throws Exception
	 */
	@PostMapping(path = "/",produces = "application/json")
	public String query(@RequestBody String query)
	throws Exception
	{
		return queryLimit(query, -1);
	}//------------------------------------------------
	@PostMapping(path = "",produces = "application/json")
	public String queryNoLimit(@RequestBody String query)
	throws Exception
	{
		return queryLimit(query, -1);
	}//------------------------------------------------
	/**
	 * Execute a GemFire query
	 * @param query the OQL to execute
	 * @param limit the results count limit
	 * @return the JSON records
	 * @throws Exception
	 */
	@PostMapping(path = "{limit}",produces = "application/json")
	public String queryLimit(@RequestBody String query, @PathVariable int limit)
	throws Exception
	{
		if(query == null || query.length() ==0)
			return null;

		  try {

			  query = this.appendLimit(query, limit);

			  Logger logger = LogManager.getLogger(getClass());
			  logger.info("QueryService: START query "+query);
	            Collection<Object> results = querierService.query(query);
	            logger.info("QueryService: END query "+query);

	            if(results == null)
	            	return null;

	            return gson.toJson(results, Collection.class);
	        }
	        catch(Exception e)
	        {
	            e.printStackTrace();
	            throw e;
	        }
	}//------------------------------------------------
	/**
     * Handling exceptions in general for REST responses
     * @param request the HTTP request
     * @param response the HTTP response
     * @param e the exception
     * @return Data Error details
     */
    @ExceptionHandler(Exception.class)
    DataError  handleException(HttpServletRequest request, HttpServletResponse response,Exception e )
    {
        return faultAgent.handleException(request,response,e);
    }//------------------------------------------------
	/**
	 *
	 * @param query the OQL to append
	 * @param limit the limit number
	 * @return the OQL appended with limit if > 0
	 */
	protected String appendLimit(String query, int limit)
	{
		if(limit <= 0 || query.contains(" limit "))
			return query;

		return new StringBuilder(query).append(" limit ").append(limit).toString();
	}

}


package gedi.solutions.geode.operations.stats;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import gedi.solutions.geode.operations.stats.visitors.StatsVisitor;

/*
 * Defines a statistic resource type. Each resource instance must be of a
 * single type. The type defines what statistics each instance of it will
 * support. The type also has a description of itself.
 */
public class ResourceType implements StatsInfo
{
	  private boolean loaded;
	    //    private final int id;
	    private final String name;
	    private String desc;
	    private final StatDescriptor[] stats;
	    private Map<String,StatDescriptor> descriptorMap;

	    public void dump(PrintWriter stream) {
	      if (loaded) {
	        stream.println(name + ": " + desc);
	        for (StatDescriptor stat : stats) {
	          stat.dump(stream);
	        }
	      }
	    }

	    protected ResourceType(String name, int statCount) {
	      this.loaded = false;
	      this.name = name;
	      this.desc = null;
	      this.stats = new StatDescriptor[statCount];
	      this.descriptorMap = null;
	    }

	    protected ResourceType(String name, String desc, int statCount) {
	      this.loaded = true;
	      this.name = name;
	      this.desc = desc;
	      this.stats = new StatDescriptor[statCount];
	      this.descriptorMap = new HashMap<String,StatDescriptor>();
	    }

	    public boolean isRegion()
	    {
	    	return name != null && name.contains("PartitionedRegionStats");
	    }

	    public boolean isLoaded() {
	      return this.loaded;
	    }

	    /*
	     * Frees up any resources no longer needed after the archive file is closed.
	     * Returns true if this guy is no longer needed.
	     */
	    protected boolean close() {
	      if (isLoaded()) {
	        for (int i = 0; i < stats.length; i++) {
	          if (stats[i] != null) {
	            if (!stats[i].isLoaded()) {
	              stats[i] = null;
	            }
	          }
	        }
	        return false;
	      } else {
	        return true;
	      }
	    }

	    void unload() {
	      this.loaded = false;
	      this.desc = null;
	      for (StatDescriptor stat : this.stats) {
	        stat.unload();
	      }
	      this.descriptorMap.clear();
	      this.descriptorMap = null;
	    }

	    protected void addStatDescriptor(GfStatsReader archive, int offset,
	        String name, boolean isCounter,
	        boolean largerBetter,
	        byte typeCode, String units, String desc) {
	      StatDescriptor descriptor = new StatDescriptor(name, offset, isCounter,
	          largerBetter, typeCode, units, desc);
	      this.stats[offset] = descriptor;
	      if (archive.loadStatDescriptor(descriptor, this)) {
	        descriptorMap.put(name, descriptor);
	      }
	    }

//	    private int getId() {
//	      return this.id;
//	    }

	    /*
	     * Returns the name of this resource type.
	     */
	    public String getName() {
	      return this.name;
	    }

	    /*
	     * Returns an array of descriptors for each statistic this resource type
	     * supports.
	     */
	    public StatDescriptor[] getStats() {
	      return this.stats;
	    }

	    /*
	     * Gets a stat descriptor contained in this type given the stats name.
	     *
	     * @param name the name of the stat to find in the current type
	     * @return the descriptor that matches the name or null if the type does not
	     * have a stat of the given name
	     */
	    public StatDescriptor getStat(String name) {
	      return (StatDescriptor) descriptorMap.get(name);
	    }

	    /*
	     * Returns a description of this resource type.
	     */
	    public String getDescription() {
	      return this.desc;
	    }

	    @Override
	    public int hashCode() {
	      final int prime = 31;
	      int result = 1;
	      result = prime * result + ((name == null) ? 0 : name.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;
	      ResourceType other = (ResourceType) obj;
	      if (name == null) {
	        if (other.name != null)
	          return false;
	      } else if (!name.equals(other.name))
	        return false;
	      return true;
	    }

		/* (non-Javadoc)
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString()
		{
			StringBuilder builder = new StringBuilder();
			builder.append("ResourceType [loaded=").append(loaded).append(", name=").append(name).append(", desc=")
					.append(desc).append(", stats=").append(Arrays.toString(stats)).append(", descriptorMap=")
					.append(descriptorMap);
			return builder.toString();
		}

		@Override
		public void accept(StatsVisitor visitor)
		{
			visitor.visitResourceType(this);

		}

}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh;

import static shillelagh.Operator.AND;
import static shillelagh.Operator.BETWEEN;
import static shillelagh.Operator.EQUAL;
import static shillelagh.Operator.GREATER_THAN_EQUAL;
import static shillelagh.Operator.GREATER_THAN;
import static shillelagh.Operator.IS;
import static shillelagh.Operator.LESS_THAN;
import static shillelagh.Operator.LESS_THAN_EQUAL;
import static shillelagh.Operator.LIKE;
import static shillelagh.Operator.NOT;
import static shillelagh.Operator.NOT_EQUAL;
import static shillelagh.Operator.NULL;
import static shillelagh.Operator.OR;
import static shillelagh.Operator.ORDER_BY;

/** A builder for building sql queries. */
public final class QueryBuilder<T> extends Builder<T> {
  private final String columnName;

  QueryBuilder(Class<? extends T> tableObject, Shillelagh shillelagh, String columnName,
      StringBuilder query) {
    super(shillelagh, tableObject, query);
    this.columnName = columnName;
  }

  public QueryBuilder<T> isEqualTo(Object value) {
    query.append(EQUAL.operator).append(valueOf(columnName, value));
    return this;
  }

  public QueryBuilder<T> isNotEqualTo(Object value) {
    query.append(NOT_EQUAL.operator).append(valueOf(columnName, value));
    return this;
  }

  public QueryBuilder<T> isGreaterThanOrEqualTo(Object value) {
    query.append(GREATER_THAN_EQUAL.operator).append(valueOf(columnName, value));
    return this;
  }

  public QueryBuilder<T> isGreaterThan(Object value) {
    query.append(GREATER_THAN.operator).append(valueOf(columnName, value));
    return this;
  }

  public QueryBuilder<T> isLessThanOrEqualTo(Object value) {
    query.append(LESS_THAN_EQUAL.operator).append(valueOf(columnName, value));
    return this;
  }

  public QueryBuilder<T> isLessThan(Object value) {
    query.append(LESS_THAN.operator).append(valueOf(columnName, value));
    return this;
  }

  public QueryBuilder<T> between(Object value1, Object value2) {
    query.append(BETWEEN.operator)
        .append(valueOf(columnName, value1))
        .append(AND.operator)
        .append(valueOf(columnName, value2));
    return this;
  }

  public QueryBuilder<T> and(String columnName) {
    checkColumnName(columnName);
    query.append(AND.operator).append(columnName);
    return this;
  }

  public QueryBuilder<T> or(String columnName) {
    checkColumnName(columnName);
    query.append(OR.operator).append(columnName);
    return this;
  }

  public QueryBuilder<T> like(Object value) {
    query.append(LIKE.operator).append("\'").append(value).append("\'");
    return this;
  }

  public QueryBuilder<T> isNotNull() {
    query.append(IS.operator).append(NOT.operator).append(NULL.operator);
    return this;
  }

  public QueryBuilder<T> isNull() {
    query.append(IS.operator).append(NULL.operator);
    return this;
  }

  public QueryBuilder<T> not() {
    query.append(NOT.operator);
    return this;
  }

  public OrderByBuilder<T> orderBy(String columnName) {
    checkColumnName(columnName);
    query.append(ORDER_BY.operator).append(columnName);
    return new OrderByBuilder<T>(shillelagh, tableObject, query);
  }

  /** Forces the value to be wrapped with ' if it isEqualTo for a string field. */
  private Object valueOf(String columnName, Object value) {
    java.lang.reflect.Field field;
    try {
      field = tableObject.getDeclaredField(columnName);
    } catch (NoSuchFieldException e) {
      throw new RuntimeException(
          String.format("%s isEqualTo not a field found in %s", columnName, tableObject));
    }
    return field.getType() == String.class ? String.format("\'%s\'", value) : value;
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh.internal;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

class SqliteInteger {
  static final List<TypeKind> INTEGER_KINDS = Arrays.asList(
          TypeKind.INT,
          TypeKind.SHORT,
          TypeKind.LONG,
          TypeKind.BYTE, // true false types
          TypeKind.BOOLEAN // true false types
  );

  static final List<String> INTEGER_OBJECTS = Arrays.asList(
          "java.lang.Integer",
          "java.lang.Short",
          "java.lang.Long",
          "java.lang.Boolean",
          "java.lang.Byte",
          "java.util.Date"
  );

  private HashSet<TypeKind> integerKinds = new HashSet<TypeKind>(INTEGER_KINDS.size());
  private HashSet<String> integerObjects = new HashSet<String>(INTEGER_OBJECTS.size());

  SqliteInteger() {
    integerKinds.addAll(INTEGER_KINDS);
    integerObjects.addAll(INTEGER_OBJECTS);
  }

  /**
   * Checks if a TypeMirror is mapped to Sqlite Integer Type
   *
   * @param typeMirror The TypeMirror to check if it's an Sqlite Integer Type
   * @return true if it maps to Sqlite Integer false otherwise
   */
  boolean isTypeOf(TypeMirror typeMirror) {
    if (integerKinds.contains(typeMirror.getKind())) {
      return true;
    }

    if (integerObjects.contains(typeMirror.toString())) {
      return true;
    }

    return false;
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh.internal;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import org.objenesis.instantiator.ObjectInstantiator;

/**
 * Set of utility functions used by code generated by shillelagh.
 */
public final class ShillelaghUtil {

  private static final Objenesis OBJENESIS = new ObjenesisStd();

  /**
   * Takes an object and serializes it to a byte array. Don't enforce that it extends serializable
   * to allow for lists and maps to be passed in.
   *
   * @param object The object to serialize. Must implement {@link java.io.Serializable}
   * @return The object as a byte array.
   */
  public static <T> byte[] serialize(T object) {
    try {
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
      objectOutputStream.writeObject(object);
      return byteArrayOutputStream.toByteArray();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * De-serialize a byte array back to it's original object.
   *
   * @param bytes The bytes that where serialized from an object.
   * @param <K> The type the bytes should be serialised back too.
   * @return The de-serialized object.
   */
  public static <K> K deserialize(byte[] bytes) {
    try {
      ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
      ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
      @SuppressWarnings("unchecked") final K k = (K) objectInputStream.readObject();
      return k;
    } catch (IOException e) {
      throw new RuntimeException(e);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Takes a class type and constructs it. If the class does not have an empty constructor this
   * will
   * find the parametrized constructor and use nulls and default values to construct the class.
   *
   * @param clazz The class to construct.
   * @param <T> The type of the class to construct.
   * @return The constructed object.
   */
  @SuppressWarnings("unchecked")
  public static <T> T createInstance(Class<T> clazz) {
    if (clazz.isInterface()) {
      if (clazz == List.class) {
        return (T) new ArrayList();
      } else if (clazz == Map.class) {
        return (T) new HashMap();
      }

      throw new UnsupportedOperationException("Interface types can not be instantiated.");
    }

    ObjectInstantiator instantiator = OBJENESIS.getInstantiatorOf(clazz);
    return (T) instantiator.newInstance();
  }

  private ShillelaghUtil() {
    /* No Instances */
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh.internal;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;

import javax.tools.StandardLocation;
import shillelagh.Column;
import shillelagh.Id;
import shillelagh.Shillelagh;
import shillelagh.Table;

public final class ShillelaghProcessor extends AbstractProcessor {
  static final boolean DEBUG = false;

  private Map<String, TableObject> oneToManyCache;

  private ShillelaghLogger logger;

  private Elements elementUtils;
  private Types typeUtils;
  private Filer filer;

  @Override public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    logger = new ShillelaghLogger(processingEnv.getMessager());

    elementUtils = processingEnv.getElementUtils();
    typeUtils = processingEnv.getTypeUtils();
    filer = processingEnv.getFiler();
  }

  @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> supportTypes = Sets.newLinkedHashSet();
    supportTypes.add(Table.class.getCanonicalName());

    return supportTypes;
  }

  @Override public boolean process(Set<? extends TypeElement> annotations,
      RoundEnvironment roundEnvironment) {

    long startTime = System.currentTimeMillis();

    Map<String, TableObject> tableObjectCache = Maps.newHashMap();
    oneToManyCache = Maps.newHashMap();

    for (TypeElement annotation : annotations) {
      Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(annotation);
      for (Element element : elements) {
        String targetType = element.toString();
        String classPackage = getPackageName(element);
        String className = getClassName((TypeElement) element, classPackage) + Shillelagh.$$SUFFIX;
        TableObject tableObject = new TableObject(element, classPackage, className, logger,
            element.getAnnotation(Table.class).name());
        logger.d("Element: " + element.toString());
        logger.d("TargetType: " + targetType);
        logger.d("ClassPackage: " + classPackage);
        logger.d("ClassName: " + className);

        // Look for fields and ids on internal elements (Fields).
        for (Element innerElement : element.getEnclosedElements()) {
          logger.d("Inner Elements: " + innerElement.getSimpleName().toString());
          logger.d(innerElement.getKind().toString());
          checkForTableId(tableObject, innerElement);
          checkForFields(tableObject, innerElement);
        }

        // TODO Check if multiple super types are supported
        // Loop through super types and parse out id/fields
        List<? extends TypeMirror> typeMirrors = typeUtils.directSupertypes(element.asType());
        for (TypeMirror typeMirror : typeMirrors) {
          logger.d("SuperType: " + typeMirror.toString());
          TypeElement typeElement = elementUtils.getTypeElement(typeMirror.toString());
          List<? extends Element> enclosedElements = typeElement.getEnclosedElements();
          for (Element enclosedElement : enclosedElements) {
            checkForTableId(tableObject, enclosedElement);
            checkForFields(tableObject, enclosedElement);
          }
        }

        logger.d(tableObject.toString());
        if (tableObject.getIdColumn() == null) {
          logger.e(String.format("%s does not have an id column. Did you forget @Id?", targetType));
        }

        tableObjectCache.put(element.toString(), tableObject);
      }
    }

    // Process one to many relationships
    for (Map.Entry<String, TableObject> entry : oneToManyCache.entrySet()) {
      logger.d("Entry: " + entry.getKey() + " " + entry.getValue());
      TableObject tableObject = tableObjectCache.get(entry.getKey());
      String columnName = entry.getValue().getTableName().toLowerCase();
      tableObject.addColumn(new TableColumn(columnName, columnName, Integer.class.getName(),
          SqliteType.ONE_TO_MANY_CHILD));
      tableObject.setIsChildTable(true);
    }

    for (TableObject tableObject : tableObjectCache.values()) {
      logger.d("Writing for " + tableObject.getTableName());

      Element element = tableObject.getOriginatingElement();

      try {
        // Write out the create statement.
        JavaFileManager.Location location = StandardLocation.SOURCE_OUTPUT;
        String path = getPackageName(element);
        String file = tableObject.getTableName() + ".sql";
        FileObject resource = filer.createResource(location, path, file, element);
        Writer writer = resource.openWriter();
        writer.write(tableObject.toString());
        writer.flush();
        writer.close();
      } catch (IOException e) {
        logger.e(
            String.format("Unable to write schema for %s, message: %s", tableObject.getTableName(),
                e.getMessage()));
      }

      try {
        JavaFileObject jfo = filer.createSourceFile(tableObject.getFqcn(), element);
        Writer writer = jfo.openWriter();
        tableObject.brewJava(writer);
        writer.flush();
        writer.close();
      } catch (IOException e) {
        logger.e(String.format("Unable to write shillelagh classes for type %s: %s", element,
            e.getMessage()));
      }
    }

    long endTime = System.currentTimeMillis() - startTime;
    logger.d("Shillelagh took %d milliseconds", endTime);

    return true;
  }

  @Override public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }

  /** Gets the package the element is in */
  private String getPackageName(Element type) {
    return elementUtils.getPackageOf(type).getQualifiedName().toString();
  }

  /** Create the fully qualified class name */
  private String getClassName(TypeElement type, String packageName) {
    int packageLen = packageName.length() + 1;
    return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
  }

  /** Check if the element has the @Id annotation if it does use that for it's id */
  private void checkForTableId(TableObject tableObject, Element element) {
    // Check if user wants to use an id other than _id
    Id idAnnotation = element.getAnnotation(Id.class);
    if (idAnnotation != null) {
      if (element.asType().getKind() != TypeKind.LONG && !("java.lang.Long".equals(
          element.asType().toString()))) {
        logger.e("@Id must be on a long");
      }

      // Id attribute set and continue
      String columnName = Strings.isBlank(idAnnotation.name()) //
          ? element.getSimpleName().toString() //
          : idAnnotation.name();

      final TableColumn idColumn = new TableColumn(columnName, element.getSimpleName().toString(),
          element.asType().toString(), SqliteType.INTEGER);

      tableObject.setIdColumn(idColumn);
    }
  }

  /**
   * Check if the element has a @Field annotation if it does parse it and
   * add it to the table object
   */
  private void checkForFields(TableObject tableObject, Element columnElement) {
    Column columnAnnotation = columnElement.getAnnotation(Column.class);
    if (columnAnnotation == null) return;

    // Convert the element from a field to a type
    final Element typeElement = typeUtils.asElement(columnElement.asType());
    final String type = typeElement == null ? columnElement.asType().toString()
        : elementUtils.getBinaryName((TypeElement) typeElement).toString();

    TableColumn tableColumn = new TableColumn(columnElement, type, columnAnnotation.name());
    if (tableColumn.isBlob() && !tableColumn.isByteArray()) {
      String columnType = columnElement.asType().toString();
      logger.d("Column Element Type: " + columnType);
      if (!checkForSuperType(columnElement, Serializable.class)
          && !columnType.equals("java.lang.Byte[]")
          && !columnType.startsWith("java.util.Map")
          && !columnType.startsWith("java.util.List")) {
        logger.e(String.format(
            "%s in %s is not Serializable and will not be able to be converted to a byte array",
            columnElement.toString(), tableObject.getTableName()));
      }
    } else if (tableColumn.isOneToMany()) {
      // List<T> should only have one generic type. Get that type and make sure
      // it has @Table annotation
      TypeMirror typeMirror = ((DeclaredType) columnElement.asType()).getTypeArguments().get(0);
      if (typeUtils.asElement(typeMirror).getAnnotation(Table.class) == null) {
        logger.e("One to many relationship in class %s where %s is not annotated with @Table",
            tableObject.getTableName(), tableColumn.getColumnName());
      }
      oneToManyCache.put(typeMirror.toString(), tableObject);
      TypeElement childColumnElement = elementUtils.getTypeElement(typeMirror.toString());
      tableColumn.setType(getClassName(childColumnElement, getPackageName(childColumnElement)));
    } else if (tableColumn.getSqlType() == SqliteType.UNKNOWN) {
      @SuppressWarnings("ConstantConditions") Table annotation =
          typeElement.getAnnotation(Table.class);
      if (annotation == null) {
        logger.e(String.format(
            "%s in %s needs to be marked as a blob or should be " + "annotated with @Table",
            columnElement.toString(), tableObject.getTableName()));
      }
      tableColumn.setOneToOne(true);
    }
    tableObject.addColumn(tableColumn);
  }

  /** Checks for a supertype returns true if element has a supertype */
  private boolean checkForSuperType(Element element, Class type) {
    List<? extends TypeMirror> superTypes = typeUtils.directSupertypes(element.asType());
    for (TypeMirror superType : superTypes) {
      if (superType.toString().equals(type.getName())) {
        return true;
      }
    }
    return false;
  }
}


/*
 * Copyright 2015 Andrew Reitz
 *
 * 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 shillelagh.internal;

import com.google.common.collect.Lists;
import com.squareup.javawriter.JavaWriter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Writer;
import java.util.Date;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.lang.model.element.Element;

import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import static shillelagh.Shillelagh.$$CREATE_TABLE_FUNCTION;
import static shillelagh.Shillelagh.$$DELETE_OBJECT_FUNCTION;
import static shillelagh.Shillelagh.$$DROP_TABLE_FUNCTION;
import static shillelagh.Shillelagh.$$GET_OBJECT_BY_ID;
import static shillelagh.Shillelagh.$$INSERT_OBJECT_FUNCTION;
import static shillelagh.Shillelagh.$$MAP_OBJECT_FUNCTION;
import static shillelagh.Shillelagh.$$MAP_SINGLE_FUNCTION;
import static shillelagh.Shillelagh.$$SUFFIX;
import static shillelagh.Shillelagh.$$UPDATE_ID_FUNCTION;
import static shillelagh.Shillelagh.$$UPDATE_OBJECT_FUNCTION;

final class TableObject {

  private static final String SERIALIZE_FUNCTION = "ShillelaghUtil.serialize";
  private static final String DESERIALIZE_FUNCTION = "ShillelaghUtil.deserialize";
  private static final String NEW_INSTANCE_FUNCTION = "ShillelaghUtil.createInstance";
  private static final String GET_ID_FUNCTION = "getId";
  private static final String SELECT_ALL_FUNCTION = "selectAll";
  private static final String PARENT_INSERT_FUNCTION = "parentInsert";
  private static final String INSERT_ONE_TO_ONE = "insertOneToOne";

  /** Used as a template to create a new table */
  private static final String CREATE_TABLE_DEFAULT =
      "CREATE TABLE %s " + "(%s INTEGER PRIMARY KEY AUTOINCREMENT, %s);";

  /**
   * SQL statement to select the id of the last inserted row. Does not end with ; in order to be
   * used with SQLiteDatabase#rawQuery(String, String[])
   */
  private static final String GET_ID_OF_LAST_INSERTED_ROW_SQL =
      "SELECT ROWID FROM %s ORDER BY ROWID DESC LIMIT 1";

  private final Element element;
  private final String classPackage;
  private final String className;
  private final ShillelaghLogger logger;
  private final String tableName;

  private boolean isChildTable = false;
  private TableColumn idColumn;

  private final List<TableColumn> columns = Lists.newLinkedList();

  TableObject(Element element, String classPackage, String className, ShillelaghLogger logger,
      String tableName) {
    this.element = element;
    this.classPackage = classPackage;
    this.className = className;
    this.logger = logger;
    this.tableName = Strings.isBlank(tableName) //
        ? element.getSimpleName().toString() //
        : tableName;
  }

  void setIdColumn(TableColumn idColumn) {
    this.idColumn = idColumn;
  }

  TableColumn getIdColumn() {
    return idColumn;
  }

  void setIsChildTable(boolean isChildTable) {
    this.isChildTable = isChildTable;
  }

  Element getOriginatingElement() {
    return element;
  }

  void addColumn(TableColumn column) {
    columns.add(column);
  }

  String getTableName() {
    return tableName;
  }

  String getTargetClass() {
    return element.toString();
  }

  // TODO Write a test for this.
  /** Get table schema */
  private String getSchema() {
    StringBuilder sb = new StringBuilder();
    Iterator<TableColumn> iterator = columns.iterator();
    while (iterator.hasNext()) {
      TableColumn column = iterator.next();
      if (column.isOneToMany()) {
        if (!iterator.hasNext()) {
          // remove the extra ", " after one to many
          int length = sb.length();
          sb.replace(length - 2, length, "");
        }
        continue;
      }
      sb.append(column);
      if (iterator.hasNext()) {
        sb.append(", ");
      }
    }

    // writes out id_missing for logging, the actual idMissing check happens
    // by the annotation processor.
    String idCol = idColumn == null ? "id_missing" : idColumn.getColumnName();
    return String.format(CREATE_TABLE_DEFAULT, getTableName(), idCol, sb.toString());
  }

  /** Get the fully qualified class name */
  String getFqcn() {
    return classPackage + "." + className;
  }

  /** Create the java functions required for the internal class */
  void brewJava(Writer writer) throws IOException {
    logger.d("brewJava");
    JavaWriter javaWriter = new JavaWriter(writer);
    javaWriter.setCompressingTypes(false);

    javaWriter.emitSingleLineComment("Generated code from Shillelagh. Do not modify!") //
        .emitPackage(classPackage) //
        /* Knows nothing of android types, must use strings. */ //
        .emitImports("android.content.ContentValues", "android.database.Cursor", //
            "android.database.DatabaseUtils", "android.database.sqlite.SQLiteDatabase") //
        .emitImports(ShillelaghUtil.class, ByteArrayInputStream.class, ByteArrayOutputStream.class,
            IOException.class, ObjectInputStream.class, ObjectOutputStream.class, LinkedList.class,
            Date.class, List.class) //
        .beginType(className, "class", EnumSet.of(PUBLIC, FINAL));

    if (this.isChildTable) {
      emitParentInsert(javaWriter);
      emitSelectAll(javaWriter);
    }
    emitInsert(javaWriter);
    emitOneToOneInsert(javaWriter);
    emitGetId(javaWriter);
    emitCreateTable(javaWriter);
    emitDropTable(javaWriter);
    emitUpdate(javaWriter);
    emitUpdateColumnId(javaWriter);
    emitDeleteWithId(javaWriter);
    emitDeleteWithObject(javaWriter);
    emitMapCursorToObject(javaWriter);
    emitSingleMap(javaWriter);
    emitSelectById(javaWriter);
    javaWriter.endType();
  }

  /** Create a way to get an id for foreign keys */
  private void emitGetId(JavaWriter javaWriter) throws IOException {
    logger.d("emitGetId");
    javaWriter.beginMethod("long", GET_ID_FUNCTION, EnumSet.of(PUBLIC, STATIC), getTargetClass(),
        "value").emitStatement("return value.%s", idColumn.getMemberName()).endMethod();
  }

  /** Creates the function for creating the table */
  private void emitCreateTable(JavaWriter javaWriter) throws IOException {
    logger.d("emitCreateTable");
    javaWriter.beginMethod("void", $$CREATE_TABLE_FUNCTION, EnumSet.of(PUBLIC, STATIC),
        "SQLiteDatabase", "db").emitStatement("db.execSQL(\"%s\")", getSchema()).endMethod();
  }

  /** Creates the function dropping the table */
  private void emitDropTable(JavaWriter javaWriter) throws IOException {
    logger.d("emitDropTable");
    javaWriter.beginMethod("void", $$DROP_TABLE_FUNCTION, EnumSet.of(PUBLIC, STATIC),
        "SQLiteDatabase", "db")
        .emitStatement("db.execSQL(\"DROP TABLE IF EXISTS %s\")", getTableName())
        .endMethod();
  }

  /** Creates the function for inserting a new value into the database */
  private void emitInsert(JavaWriter javaWriter) throws IOException {
    logger.d("emitInsert");
    String tableName = getTableName();
    javaWriter.beginMethod("void", $$INSERT_OBJECT_FUNCTION, EnumSet.of(PUBLIC, STATIC),
        getTargetClass(), "element", "SQLiteDatabase", "db")
        .emitStatement("ContentValues values = new ContentValues()");
    List<TableColumn> childColumns = Lists.newLinkedList();
    for (TableColumn column : columns) {
      String columnName = column.getColumnName();
      String memberName = column.getMemberName();
      if (column.isBlob() && !column.isByteArray()) {
        javaWriter.emitStatement("values.put(\"%s\", %s(element.%s))", columnName,
            SERIALIZE_FUNCTION, memberName);
      } else if (column.isOneToOne()) {
        javaWriter.emitStatement("%s%s.%s(element.%s, db)", column.getType(), $$SUFFIX,
            INSERT_ONE_TO_ONE, column.getColumnName())
            .emitStatement("values.put(\"%s\", %s%s.%s(element.%s))", columnName, column.getType(),
                $$SUFFIX, GET_ID_FUNCTION, memberName);
      } else if (column.isDate()) {
        javaWriter.emitStatement("values.put(\"%s\", element.%s.getTime())", columnName,
            memberName);
      } else if (column.isOneToMany()) {
        childColumns.add(column);
      } else if (!column.isOneToManyChild()) {
        javaWriter.emitStatement("values.put(\"%s\", element.%s)", columnName, memberName);
      }
    }
    javaWriter.emitStatement("db.insert(\"%s\", null, values)", tableName);

    if (!childColumns.isEmpty()) {
      javaWriter.emitStatement("long id = DatabaseUtils.longForQuery(db, "
          + "\"SELECT ROWID FROM %s ORDER BY ROWID DESC LIMIT 1\", null)", tableName);
    }
    for (TableColumn childColumn : childColumns) {
      javaWriter.emitStatement("%s%s.%s(id, element.%s, db)", childColumn.getType(), $$SUFFIX,
          PARENT_INSERT_FUNCTION, childColumn.getMemberName());
    }

    javaWriter.endMethod();
  }

  /** Creates the function for updating an object */
  private void emitUpdate(JavaWriter javaWriter) throws IOException {
    logger.d("emitUpdate");
    javaWriter.beginMethod("void", $$UPDATE_OBJECT_FUNCTION, EnumSet.of(PUBLIC, STATIC),
        getTargetClass(), "element", "SQLiteDatabase", "db")
        .emitStatement("ContentValues values = new ContentValues()");

    for (TableColumn column : columns) {
      String columnName = column.getColumnName();
      String memberName = column.getMemberName();
      if (column.getSqlType() == SqliteType.BLOB && !column.isByteArray()) {
        javaWriter.emitStatement("values.put(\"%s\", %s(element.%s))", columnName,
            SERIALIZE_FUNCTION, memberName);
      } else if (column.isOneToOne()) {
        javaWriter.emitStatement("values.put(\"%s\", %s%s.%s(element.%s))", columnName,
            column.getType(), $$SUFFIX, GET_ID_FUNCTION, memberName);
      } else if (column.isDate()) {
        javaWriter.emitStatement("values.put(\"%s\", element.%s.getTime())", columnName,
            memberName);
      } else if (column.isOneToMany()) {
        javaWriter.beginControlFlow("for (%s child : element.%s)",
            column.getType().replace("$", "."), column.getColumnName())
            .emitStatement("%s%s.%s(child, db)", column.getType(), $$SUFFIX,
                $$UPDATE_OBJECT_FUNCTION)
            .endControlFlow();
      } else if (column.isOneToManyChild()) {
        // TODO: actually no way of actually updating this value directly add a wrapper?
      } else {
        javaWriter.emitStatement("values.put(\"%s\", element.%s)", columnName, memberName);
      }
    }
    javaWriter.emitStatement("db.update(\"%s\", values, \"%s = \" + element.%s, null)",
        getTableName(), idColumn.getColumnName(), idColumn.getMemberName());
    javaWriter.endMethod();
  }

  /** Updates the id of the object to the last insert */
  private void emitUpdateColumnId(JavaWriter javaWriter) throws IOException {
    logger.d("emitUpdateColumnId");
    // Updates the column id for the last inserted row
    javaWriter.beginMethod("void", $$UPDATE_ID_FUNCTION, EnumSet.of(PUBLIC, STATIC),
        getTargetClass(), "element", "SQLiteDatabase", "db")
        .emitStatement("long id = DatabaseUtils.longForQuery(db, \"%s\", null)",
            String.format(GET_ID_OF_LAST_INSERTED_ROW_SQL, getTableName()))
        .emitStatement("element.%s = id", idColumn.getMemberName())
        .endMethod();
  }

  /** Creates the function for deleting an object by id */
  private void emitDeleteWithId(JavaWriter javaWriter) throws IOException {
    logger.d("emitDeleteWithId");
    javaWriter.beginMethod("void", $$DELETE_OBJECT_FUNCTION, EnumSet.of(PUBLIC, STATIC), "Long",
        "id", "SQLiteDatabase", "db")
        .emitStatement("db.delete(\"%s\", \"%s = \" + id, null)", getTableName(),
            idColumn.getColumnName())
        .endMethod();
  }

  /** Creates the function for deleting an object from the table */
  private void emitDeleteWithObject(JavaWriter javaWriter) throws IOException {
    logger.d("emitDeleteWithObject");
    javaWriter.beginMethod("void", $$DELETE_OBJECT_FUNCTION, EnumSet.of(PUBLIC, STATIC),
        getTargetClass(), "element", "SQLiteDatabase", "db")
        .emitStatement("%s(element.%s, db)", $$DELETE_OBJECT_FUNCTION, idColumn.getMemberName())
        .endMethod();
  }

  /** Creates the function for mapping a cursor to the object after executing a sql statement */
  private void emitMapCursorToObject(JavaWriter javaWriter) throws IOException {
    logger.d("emitMapCursorToObject");

    final String targetClass = getTargetClass();

    javaWriter.beginMethod("List<" + targetClass + ">", $$MAP_OBJECT_FUNCTION,
        EnumSet.of(PUBLIC, STATIC), "Cursor", "cursor", "SQLiteDatabase", "db")
        .emitStatement("List<%s> tableObjects = new LinkedList<%s>()", targetClass, targetClass)
        .beginControlFlow("if (cursor.moveToFirst())")
        .beginControlFlow("while (!cursor.isAfterLast())")
        .emitStatement("%s tableObject = %s(cursor, db)", targetClass, $$MAP_SINGLE_FUNCTION)
        .emitStatement("tableObjects.add(tableObject)")
        .emitStatement("cursor.moveToNext()")
        .endControlFlow()
        .endControlFlow()
        .emitStatement("return tableObjects")
        .endMethod();
  }

  private void emitSingleMap(JavaWriter javaWriter) throws IOException {
    String targetClass = getTargetClass();
    javaWriter.beginMethod(targetClass, $$MAP_SINGLE_FUNCTION, EnumSet.of(PUBLIC, STATIC), "Cursor",
        "cursor", "SQLiteDatabase", "db")
        .emitStatement("%s tableObject = %s(%s.class)", targetClass, NEW_INSTANCE_FUNCTION,
            getTargetClass())
        .emitStatement("tableObject.%s = cursor.getLong(cursor.getColumnIndex(\"%s\"))",
            idColumn.getMemberName(), idColumn.getColumnName());

    for (TableColumn column : columns) {
      String columnName = column.getColumnName();
      String columnMember = column.getMemberName();
      if (column.isDate()) {
        javaWriter.emitStatement(
            "tableObject.%s = new Date(cursor.%s(cursor.getColumnIndex(\"%s\")))", columnMember,
            CursorFunctions.get(long.class.getName()), columnName);
      } else if (column.isOneToOne()) {
        javaWriter.emitStatement(
            "tableObject.%s = %s%s.%s(cursor.%s(cursor.getColumnIndex(\"%s\")), db)", columnMember,
            column.getType(), $$SUFFIX, $$GET_OBJECT_BY_ID,
            CursorFunctions.get(Long.class.getName()), columnName);
      } else if (column.isBoolean()) {
        javaWriter.emitStatement("tableObject.%s = cursor.%s(cursor.getColumnIndex(\"%s\")) == 1",
            columnMember, CursorFunctions.get(column.getType()), columnName);
      } else if (column.getSqlType() == SqliteType.BLOB) {
        if (column.isByteArray()) {
          javaWriter.emitStatement("tableObject.%s = cursor.%s(cursor.getColumnIndex(\"%s\"))",
              columnMember, CursorFunctions.get(column.getType()), columnName);
        } else {
          javaWriter.emitStatement("tableObject.%s = %s(cursor.%s(cursor.getColumnIndex(\"%s\")));",
              columnMember, DESERIALIZE_FUNCTION, CursorFunctions.get(column.getType()),
              columnName);
        }
      } else if (column.isOneToMany()) {
        javaWriter.emitStatement("Cursor childCursor = %s%s.%s(db)", column.getType(), $$SUFFIX,
            SELECT_ALL_FUNCTION)
            .emitStatement("tableObject.%s = %s%s.%s(childCursor, db)", column.getMemberName(),
                column.getType(), $$SUFFIX, $$MAP_OBJECT_FUNCTION)
            .emitStatement("childCursor.close()");
      } else if (column.isOneToManyChild()) {
        // TODO Skip and have custom mapping?
      } else {
        javaWriter.emitStatement("tableObject.%s = cursor.%s(cursor.getColumnIndex(\"%s\"))",
            columnMember, CursorFunctions.get(column.getType()), columnName);
      }
    }

    javaWriter.emitStatement("return tableObject").endMethod();
  }

  /** Creates function for getting an object by value */
  private void emitSelectById(JavaWriter javaWriter) throws IOException {
    logger.d("emitSelectById");
    javaWriter.beginMethod(getTargetClass(), $$GET_OBJECT_BY_ID, EnumSet.of(PUBLIC, STATIC), "long",
        "id", "SQLiteDatabase", "db")
        .emitStatement("Cursor cursor = db.rawQuery(\"SELECT * FROM %s WHERE %s  = id\", null)",
            getTableName(), idColumn.getColumnName())
        .emitStatement("%s value = %s(cursor, db).get(0)", getTargetClass(), $$MAP_OBJECT_FUNCTION)
        .emitStatement("cursor.close()")
        .emitStatement("return value")
        .endMethod();
  }

  private void emitParentInsert(JavaWriter javaWriter) throws IOException {
    javaWriter.beginMethod("void", PARENT_INSERT_FUNCTION, EnumSet.of(PUBLIC, STATIC), "long",
        "parentId", String.format("List<%s>", getTargetClass()), "children", "SQLiteDatabase", "db")
        .beginControlFlow("for(%s child : children)", getTargetClass())
        .emitStatement("ContentValues values = new ContentValues()");
    for (TableColumn column : columns) {
      String columnName = column.getColumnName();
      String columnMember = column.getMemberName();
      if (column.getSqlType() == SqliteType.BLOB && !column.isByteArray()) {
        javaWriter.emitStatement("values.put(\"%s\", %s(child.%s))", columnName, SERIALIZE_FUNCTION,
            columnMember);
      } else if (column.isOneToOne()) {
        javaWriter.emitStatement("values.put(\"%s\", %s%s.%s(child.%s))", columnName,
            column.getType(), $$SUFFIX, GET_ID_FUNCTION, columnMember);
      } else if (column.isDate()) {
        javaWriter.emitStatement("values.put(\"%s\", child.%s.getTime())", columnName,
            columnMember);
      } else if (column.getSqlType() == SqliteType.ONE_TO_MANY) {
        javaWriter.emitStatement("%s.%s.%s(%s)", column.getType(), $$SUFFIX, PARENT_INSERT_FUNCTION,
            columnName);
      } else if (column.getSqlType() == SqliteType.ONE_TO_MANY_CHILD) {
        javaWriter.emitStatement("values.put(\"%s\", %s)", columnName, "parentId");
      } else {
        javaWriter.emitStatement("values.put(\"%s\", child.%s)", columnName, columnMember);
      }
    }
    javaWriter.emitStatement("db.insert(\"%s\", null, values)", getTableName())
        .emitStatement("long id = DatabaseUtils.longForQuery(db, \"SELECT ROWID FROM %s "
            + "ORDER BY ROWID DESC LIMIT 1\", null)", getTableName())
        .emitStatement("child.%s = id", idColumn.getMemberName())
        .endControlFlow()
        .endMethod();
  }

  private void emitSelectAll(JavaWriter javaWriter) throws IOException {
    logger.d("emitSelectAll");
    javaWriter.beginMethod("Cursor", SELECT_ALL_FUNCTION, EnumSet.of(PUBLIC, STATIC),
        "SQLiteDatabase", "db")
        .emitStatement("return db.rawQuery(\"SELECT * FROM %s\", null)", getTableName())
        .endMethod();
  }

  private void emitOneToOneInsert(JavaWriter javaWriter) throws IOException {
    logger.d("emitOneToOneInsert");
    javaWriter.beginMethod("void", INSERT_ONE_TO_ONE, EnumSet.of(PUBLIC, STATIC), getTargetClass(),
        "element", "SQLiteDatabase", "db")
        .emitStatement("%s(element, db)", $$INSERT_OBJECT_FUNCTION)
        .emitStatement("long id = DatabaseUtils.longForQuery(db, \"%s\", null)",
            String.format(GET_ID_OF_LAST_INSERTED_ROW_SQL, getTableName()))
        .emitStatement("element.%s = id", idColumn.getMemberName())
        .endMethod();
  }

  /** Retruns the create statement for this table object */
  @Override public String toString() {
    return getSchema();
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh;

import android.database.Cursor;
import java.util.List;
import rx.Observable;

import static shillelagh.Shillelagh.HAS_RX_JAVA;

/** Builder for turning sql statements into lists, observables or cursors */
public class Builder<T> {
  final StringBuilder query;

  final Class<? extends T> tableObject;
  final Shillelagh shillelagh;

  Builder(Shillelagh shillelagh, Class<? extends T> tableObject, StringBuilder query) {
    this.shillelagh = shillelagh;
    this.tableObject = tableObject;
    this.query = query;
  }

  /** Executes a query and returns the results as a list */
  public final List<T> toList() {
    return shillelagh.rawQuery(tableObject, query.toString());
  }

  /** Executes a query and returns the results wrapped in an observable */
  public final Observable<T> toObservable() {
    if (!HAS_RX_JAVA) {
      throw new RuntimeException(
          "RxJava not available! Add RxJava to your build to use this feature");
    }

    return shillelagh.getObservable(tableObject, new CursorLoader() {
      @Override public Cursor getCursor() {
        return shillelagh.rawQuery(query.toString());
      }
    });
  }

  /** Executes a query and returns the results in a cursor */
  public final Cursor toCursor() {
    return shillelagh.rawQuery(query.toString());
  }

  /** Returns the created query as a string */
  @Override public final String toString() {
    return query.toString().trim();
  }

  /** Check to ensure that the column name provided is valid */
  final void checkColumnName(String columnName) {
    try {
      tableObject.getDeclaredField(columnName);
    } catch (NoSuchFieldException e) {
      throw new RuntimeException(
          String.format("%s isEqualTo not a field found in %s", columnName, tableObject));
    }
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh.internal;

final class Strings {

  private Strings() {
    // No instances.
  }

  /** Checks if the string is blank, like TextUtils.isEmpty() but better */
  static boolean isBlank(CharSequence string) {
    return (string == null || string.toString().trim().length() == 0);
  }

  /** Checks if a string there, if not returns the default string */
  static String valueOrDefault(String string, String defaultString) {
    return isBlank(string) ? defaultString : string;
  }

  /** Truncates the string at the length specified */
  static String truncateAt(String string, int length) {
    return string.length() > length ? string.substring(0, length) : string;
  }

  /** Capitalizes the first letter of the string passed in */
  static String capitalize(String string) {
    if (isBlank(string)) {
      return "";
    }
    char first = string.charAt(0);
    if (Character.isUpperCase(first)) {
      return string;
    } else {
      return Character.toUpperCase(first) + string.substring(1);
    }
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh.internal;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

class SqliteReal {
  static final List<TypeKind> REAL_KINDS = Arrays.asList(
          TypeKind.DOUBLE,
          TypeKind.FLOAT
  );

  static final List<String> REAL_OBJECTS = Arrays.asList(
          "java.lang.Double",
          "java.lang.Float",
          "java.lang.Number"
  );

  private HashSet<TypeKind> realKinds = new HashSet<TypeKind>(REAL_KINDS);
  private HashSet<String> realObjects = new HashSet<String>(REAL_OBJECTS);

  SqliteReal() {
    realKinds.addAll(REAL_KINDS);
    realObjects.addAll(REAL_OBJECTS);
  }

  /**
   * Checks if a TypeMirror is mapped to Sqlite Type
   *
   * @param typeMirror The TypeMirror to check if it's an Sqlite Type
   * @return true if it maps to the Sqlite Object false otherwise
   */
  boolean isTypeOf(TypeMirror typeMirror) {
    if (realKinds.contains(typeMirror.getKind())) {
      return true;
    }

    if (realObjects.contains(typeMirror.toString())) {
      return true;
    }

    return false;
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh;

/** Builder for queries after the order by statement has been called */
public final class OrderByBuilder<T> extends Builder<T> {
  OrderByBuilder(Shillelagh shillelagh, Class<? extends T> tableObject, StringBuilder query) {
    super(shillelagh, tableObject, query);
  }

  /** Order the results in ascending order */
  public Builder<T> ascending() {
    this.query.append(" ASC");
    return this;
  }

  /** Order the results in descending order */
  public Builder<T> descending() {
    this.query.append(" DESC");
    return new Builder<T>(shillelagh, tableObject, query);
  }
}


package io.silverspoon.device;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

/**
 * Class to represent a OneWire Temperature sensor and read its values.
 *
 * @author sbunciak
 * @see {@link TemperatureSensor}
 */
public class OneWireTemperatureSensor implements TemperatureSensor {
   private File deviceFile = null;

   /**
    * Constructor.
    * @param deviceFile
    */
   public OneWireTemperatureSensor(File deviceFile) {
      this.deviceFile = deviceFile;
   }

   /**
    * Read temperature from the sensors.
    * @return temperature
    * @throws IOException
    * @see {@link TemperatureSensor#readTemperature()}
    */
   public float readTemperature() throws IOException {

      byte[] encoded = Files.readAllBytes(new File(deviceFile, "w1_slave").toPath());

      String tmp = new String(encoded);
      int tmpIndex = tmp.indexOf("t=");

      if (tmpIndex < 0) {
         throw new IOException("Could not read temperature!");
      }

      return Integer.parseInt(tmp.substring(tmpIndex + 2).trim()) / 1000f;
   }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh.internal;

import java.util.HashMap;

final class CursorFunctions {
  private static final String BLOB = "blob";
  private static final HashMap<String, String> SUPPORTED_CURSOR_METHODS
      = new HashMap<String, String>();

  static {
    final String cursorFunctionInt = "getInt";
    final String cursorFunctionDouble = "getDouble";
    final String cursorFunctionFloat = "getFloat";
    final String cursorFunctionLong = "getLong";
    final String cursorFunctionShort = "getShort";
    final String cursorFunctionString = "getString";
    final String cursorFunctionBlob = "getBlob";

    SUPPORTED_CURSOR_METHODS.put(int.class.getName(), cursorFunctionInt);
    SUPPORTED_CURSOR_METHODS.put(Integer.class.getName(), cursorFunctionInt);
    SUPPORTED_CURSOR_METHODS.put(boolean.class.getName(), cursorFunctionInt);
    SUPPORTED_CURSOR_METHODS.put(Boolean.class.getName(), cursorFunctionInt);
    SUPPORTED_CURSOR_METHODS.put(double.class.getName(), cursorFunctionDouble);
    SUPPORTED_CURSOR_METHODS.put(Double.class.getName(), cursorFunctionDouble);
    SUPPORTED_CURSOR_METHODS.put(float.class.getName(), cursorFunctionFloat);
    SUPPORTED_CURSOR_METHODS.put(Float.class.getName(), cursorFunctionFloat);
    SUPPORTED_CURSOR_METHODS.put(long.class.getName(), cursorFunctionLong);
    SUPPORTED_CURSOR_METHODS.put(Long.class.getName(), cursorFunctionLong);
    SUPPORTED_CURSOR_METHODS.put(short.class.getName(), cursorFunctionShort);
    SUPPORTED_CURSOR_METHODS.put(Short.class.getName(), cursorFunctionShort);
    SUPPORTED_CURSOR_METHODS.put(String.class.getName(), cursorFunctionString);
    SUPPORTED_CURSOR_METHODS.put(BLOB, cursorFunctionBlob);
  }

  /**
   * Maps a type to the corresponding Cursor get function. For mapping objects between the database
   * and java. If a type is not found getBlob is returned
   */
  public static String get(String type) {
    final String returnValue = SUPPORTED_CURSOR_METHODS.get(type);
    return returnValue != null ? returnValue : SUPPORTED_CURSOR_METHODS.get(BLOB);
  }

  private CursorFunctions() {
    throw new UnsupportedOperationException();
  }
}


/*
 * Copyright 2014 Andrew Reitz
 *
 * 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 shillelagh.internal;

import javax.annotation.processing.Messager;

import static javax.tools.Diagnostic.Kind.ERROR;
import static javax.tools.Diagnostic.Kind.NOTE;
import static shillelagh.internal.ShillelaghProcessor.DEBUG;

final class ShillelaghLogger {
  private final Messager messenger;

  ShillelaghLogger(Messager messenger) {
    this.messenger = messenger;
  }

  /**
   * Print out debug logs, will only print if {@link shillelagh.internal.ShillelaghProcessor#DEBUG}
   * is true.
   */
  void d(String message, Object... args) {
    if (DEBUG) {
      messenger.printMessage(NOTE, formatString(message, args));
    }
  }

  /** Print out notes */
  void n(String message, Object... args) {
    messenger.printMessage(NOTE, formatString(message, args));
  }

  /** Print out errors, this will stop the build from succeeding */
  void e(String message, Object... args) {
    messenger.printMessage(ERROR, formatString(message, args));
  }

  private String formatString(String message, Object... args) {
    return args.length == 0 ? message : String.format(message, args);
  }
}


package jiconfont.bundle;

import jiconfont.IconCode;
import jiconfont.IconFont;

import java.util.ArrayList;
import java.util.List;
import jiconfont.icons.elusive.Elusive;
import jiconfont.icons.entypo.Entypo;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.icons.google_material_design_icons.GoogleMaterialDesignIcons;
import jiconfont.icons.iconic.Iconic;
import jiconfont.icons.typicons.Typicons;

/**
 * Copyright (c) 2016 jIconFont

 *

 * 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.
 */
public class Bundle {

    /**
     * Returns all icon fonts from bundle.
     *
     * @return List with the fonts.
     */
    public static List<IconFont> getIconFonts() {
        List<IconFont> list = new ArrayList<>();
        list.add(GoogleMaterialDesignIcons.getIconFont());
        list.add(Elusive.getIconFont());
        list.add(Entypo.getIconFont());
        list.add(FontAwesome.getIconFont());
        list.add(Iconic.getIconFont());
        list.add(Typicons.getIconFont());
        return list;
    }

    /**
     * Returns all icons from bundle.
     *
     * @return List with the icons.
     */
    public static List<IconCode> getIcons() {
        List<IconCode> list = new ArrayList<>();

        for (IconCode icon : GoogleMaterialDesignIcons.values()) {
            list.add(icon);
        }
        for (IconCode icon : Elusive.values()) {
            list.add(icon);
        }
        for (IconCode icon : Entypo.values()) {
            list.add(icon);
        }
        for (IconCode icon : FontAwesome.values()) {
            list.add(icon);
        }
        for (IconCode icon : Iconic.values()) {
            list.add(icon);
        }
        for (IconCode icon : Typicons.values()) {
            list.add(icon);
        }

        return list;
    }
}


/**
 * Copyright (C) 2014-2019 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.numbercruncher.mathutils;

/**
 * Differential equation solver that implements Euler's algorithm.
 */
public class EulersDiffEqSolver extends AbstractDiffEqSolver
{
  /**
   * Constructor.
   *
   * @param equation
   *        the differential equation to solve
   */
  public EulersDiffEqSolver (final AbstractDifferentialEquation equation)
  {
    super (equation);
  }

  /**
   * Return the next data point in the approximation of the solution.
   *
   * @param h
   *        the width of the interval
   */
  @Override
  public DataPoint nextPoint (final float h)
  {
    m_fY += h * m_aEquation.at (m_fX, m_fY);
    m_fX += h;

    return new DataPoint (m_fX, m_fY);
  }
}


/**
 * Copyright (C) 2014-2019 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.numbercruncher.mathutils;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * A polynomial interpolation function.
 */
public class InterpolationPolynomial implements IEvaluatable
{
  /** number of data points */
  private int m_nDataPoints;
  /** array of data points */
  private final DataPoint [] m_aData;
  /** divided difference table */
  private final float [] [] m_aDivDiff;

  /**
   * Constructor.
   *
   * @param data
   *        the array of data points
   */
  @SuppressFBWarnings ("EI_EXPOSE_REP2")
  public InterpolationPolynomial (final DataPoint [] data)
  {
    m_aData = data;
    m_aDivDiff = new float [data.length] [data.length];

    for (final DataPoint element : data)
    {
      addDataPoint (element);
    }
  }

  /**
   * Constructor.
   *
   * @param maxPoints
   *        the maximum number of data points
   */
  public InterpolationPolynomial (final int maxPoints)
  {
    this.m_aData = new DataPoint [maxPoints];
    this.m_aDivDiff = new float [m_aData.length] [m_aData.length];
  }

  /**
   * Return the data points.
   *
   * @return the array of data points
   */
  @SuppressFBWarnings ("EI_EXPOSE_REP")
  public DataPoint [] getDataPoints ()
  {
    return m_aData;
  }

  /**
   * Return the divided difference table.
   *
   * @return the table
   */
  @SuppressFBWarnings ("EI_EXPOSE_REP")
  public float [] [] getDividedDifferenceTable ()
  {
    return m_aDivDiff;
  }

  /**
   * Return the current number of data points.
   *
   * @return the count
   */
  public int getDataPointCount ()
  {
    return m_nDataPoints;
  }

  /**
   * Add new data point: Augment the divided difference table by appending a new
   * entry at the bottom of each column.
   *
   * @param dataPoint
   *        the new data point
   */
  public void addDataPoint (final DataPoint dataPoint)
  {
    if (m_nDataPoints >= m_aData.length)
      return;

    m_aData[m_nDataPoints] = dataPoint;
    m_aDivDiff[m_nDataPoints][0] = dataPoint.getY ();

    ++m_nDataPoints;

    for (int order = 1; order < m_nDataPoints; ++order)
    {
      final int bottom = m_nDataPoints - order - 1;
      final float numerator = m_aDivDiff[bottom + 1][order - 1] - m_aDivDiff[bottom][order - 1];
      final float denominator = m_aData[bottom + order].getX () - m_aData[bottom].getX ();

      m_aDivDiff[bottom][order] = numerator / denominator;
    }
  }

  /**
   * Return the value of the polynomial interpolation function at x.
   * (Implementation of Evaluatable.)
   *
   * @param x
   *        the value of x
   * @return the value of the function at x
   */
  public float at (final float x)
  {
    if (m_nDataPoints < 2)
      return Float.NaN;

    float y = m_aDivDiff[0][0];
    float xFactor = 1;

    // Compute the value of the function.
    for (int order = 1; order < m_nDataPoints; ++order)
    {
      xFactor = xFactor * (x - m_aData[order - 1].getX ());
      y = y + xFactor * m_aDivDiff[0][order];
    }

    return y;
  }

  /**
   * Reset.
   */
  public void reset ()
  {
    m_nDataPoints = 0;
  }
}


/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the Apache
 * License, Version 2.0. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */
package org.geomajas.sld.editor.expert.gwt.client;

import java.util.LinkedHashMap;
import java.util.List;

import org.geomajas.codemirror.client.widget.CodeMirrorPanel;
import org.geomajas.sld.editor.expert.common.client.SldEditor;
import org.geomajas.sld.editor.expert.common.client.SldEditorWidgetPresenter;
import org.geomajas.sld.editor.expert.common.client.SldEditorWidgetResource;
import org.geomajas.sld.editor.expert.common.client.SldEditorWidgetView;
import org.geomajas.sld.editor.expert.common.client.domain.SldInfo;
import org.geomajas.sld.editor.expert.common.client.i18n.SldEditorExpertMessages;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.WidgetCanvas;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.events.ChangeEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangeHandler;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.toolbar.ToolStrip;
import com.smartgwt.client.widgets.toolbar.ToolStripButton;

/**
 * Implementation of {@link org.geomajas.sld.editor.expert.common.client.SldEditorWidgetView}.
 *
 * @author Kristof Heirwegh
 * @author David Debuck
 */
public class SldEditorWidgetViewImpl implements SldEditorWidgetView {

	private SldEditorExpertMessages msg = GWT.create(SldEditorExpertMessages.class);

	private VLayout widget;

	private SldEditorWidgetPresenter presenter;

	private CodeMirrorPanel codeMirrorPanel;

	private ToolStrip toolStrip;

	private SelectItem selectTemplate;

	/**
	 * UI binder interface.
	 *
	 * @author David Debuck
	 *
	 */
	interface MyUiBinder extends UiBinder<Widget, SldEditorWidgetViewImpl> {
	}

	/**
	 * SldEditorWidgetViewImpl constructor.
	 */
	public SldEditorWidgetViewImpl() {
		this(SldEditor.getInstance().getBundleFactory().createSldEditorWidgetResource());
	}

	/**
	 * SldEditorWidgetViewImpl constructor.
	 *
	 * @param resource SldEditorWidgetResource
	 */
	public SldEditorWidgetViewImpl(SldEditorWidgetResource resource) {

		resource.css().ensureInjected();

		widget = createSldEditorLayout();

	}

	/**
	 * Create the Sld Editor layout and attach handlers to the actions.
	 *
	 * @return the Sld Editor layout
	 */
	public VLayout createSldEditorLayout() {

		final VLayout vLayout = new VLayout();

		toolStrip = new ToolStrip();
		toolStrip.setWidth100();

		codeMirrorPanel = new CodeMirrorPanel();
		WidgetCanvas canvas = new WidgetCanvas(codeMirrorPanel);
		canvas.setWidth100();
		canvas.setHeight100();

		vLayout.addMember(toolStrip);
		vLayout.addMember(canvas);

		ToolStripButton saveButton = new ToolStripButton();
		saveButton.setIcon("[ISOMORPHIC]/" + "icons/silk/disk.png");
		saveButton.setTitle(msg.saveButtonTitle());
		saveButton.setTooltip(msg.saveButtonTooltip());
		saveButton.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent clickEvent) {
				presenter.onSaveButton();
			}
		});

		ToolStripButton cancelButton = new ToolStripButton();
		cancelButton.setIcon("[ISOMORPHIC]/" + "icons/silk/cancel.png");
		cancelButton.setTitle(msg.cancelButtonTitle());
		cancelButton.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent clickEvent) {
				presenter.onCancelButton();
			}
		});

		ToolStripButton validateButton = new ToolStripButton();
		validateButton.setIcon("[ISOMORPHIC]/" + "icons/silk/tick.png");
		validateButton.setTitle(msg.validateButtonTitle());
		validateButton.setTooltip(msg.validateButtonTooltip());
		validateButton.addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent clickEvent) {
				presenter.onValidateButton();
			}
		});

		ToolStripButton formatBtn = new ToolStripButton();
		formatBtn.setIcon("[ISOMORPHIC]/" + "icons/silk/text_align_left.png");
		formatBtn.setTitle(msg.formatButtonTitle());
		formatBtn.setTooltip(msg.formatButtonTooltip());
		formatBtn.addClickHandler(new ClickHandler() {

			public void onClick(ClickEvent event) {
				presenter.onFormatButton();
			}
		});

		selectTemplate = new SelectItem();
		selectTemplate.setTitle(msg.templateSelectTitle());
		selectTemplate.setTooltip(msg.templateSelectTooltip());
		selectTemplate.setWidth(200);
		selectTemplate.addChangeHandler(new ChangeHandler() {

			@Override
			public void onChange(ChangeEvent changeEvent) {
				presenter.onTemplateSelect((String) changeEvent.getValue());
			}
		});

		toolStrip.addFormItem(selectTemplate);
		toolStrip.addButton(saveButton);
		toolStrip.addButton(validateButton);
		toolStrip.addButton(formatBtn);
		/*
		 * toolStrip.addSeparator(); toolStrip.addButton(saveButton);
		 */
		toolStrip.addFill();
		toolStrip.addButton(cancelButton);

		return vLayout;

	}

	@Override
	public void setPresenter(SldEditorWidgetPresenter presenter) {

		this.presenter = presenter;

	}

	@Override
	public void showMessage(String message) {

		SC.say(message);

	}

	@Override
	public void setSldTemplates(final List<SldInfo> sldTemplates) {

		Scheduler.get().scheduleDeferred(new Command() {

			public void execute() {

				LinkedHashMap<String, String> data = new LinkedHashMap<String, String>();
				for (SldInfo info : sldTemplates) {
					data.put(info.getName(), info.getTitle() != null ? info.getTitle() : info.getName());
				}

				selectTemplate.setValueMap(data);

			}
		});

	}

	@Override
	public void setSldData(String xmlData) {

		codeMirrorPanel.getEditor().setContent(xmlData);

	}

	@Override
	public String getSldData() {

		return codeMirrorPanel.getEditor().getContent();

	}

	@Override
	public void cancelButtonEvent() {

		codeMirrorPanel.getEditor().setContent("");
		codeMirrorPanel.getEditor().clearHistory();

	}

	@Override
	public Widget asWidget() {

		return widget;

	}

}


/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the Apache
 * License, Version 2.0. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */
package org.geomajas.sld.editor.expert.gwt.client;

import org.geomajas.gwt.example.base.SamplePanel;
import org.geomajas.gwt.example.base.SamplePanelFactory;
import org.geomajas.sld.editor.expert.common.client.SldEditor;
import org.geomajas.sld.editor.expert.common.client.SldEditorWidget;
import org.geomajas.sld.editor.expert.common.client.event.SldCancelEvent;
import org.geomajas.sld.editor.expert.common.client.event.SldSaveEvent;
import org.geomajas.sld.editor.expert.common.client.i18n.SldEditorExpertMessages;

import com.google.gwt.core.client.GWT;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.HTMLFlow;
import com.smartgwt.client.widgets.Window;

/**
 * Entry point of SmartGWT SLD editor.
 *
 * @author Kristof Heirwegh
 * @author David Debuck
 */
public class SldEditorPanel extends SamplePanel {

	private static final SldEditorExpertMessages MSG = GWT.create(SldEditorExpertMessages.class);

	/**
	 * This is called by the showcase when clicking on the tree node.
	 */
	public static final SamplePanelFactory FACTORY = new SamplePanelFactory() {
		public SamplePanel createPanel() {
			return new SldEditorPanel();
		}
	};

	/**
	 * Create background content decoration for the widget tab.
	 *
	 * @return HTMLFlow background content.
	 */
	private HTMLFlow getBackgroundDecoration() {

		// Show some background content in the tab.
		HTMLFlow htmlFlow = new HTMLFlow();
		htmlFlow.setWidth100();
		htmlFlow.setHeight100();
		String contents =
			"
GEOMAJAS
"
			+ "
SLD-Editor
"
			+ "
EXPERT-mode
";
		htmlFlow.setContents(contents);

		return htmlFlow;

	}

	@Override
	public Canvas getViewPanel() {

		final Window window = new Window();
		window.setAutoCenter(true);
		window.setShowMinimizeButton(false);
		window.setShowCloseButton(true);
		window.setCanDragResize(true);
		window.setShowResizer(true);
		window.setSize("700px", "400px");
		window.setTitle(MSG.windowTitle());

		// Create a new Sld Editor Widget.
		final SldEditorWidget widget = new SldEditorWidget();

		SldEditor.getInstance().getEventBus().addHandler(SldSaveEvent.getType(), new SldSaveEvent.SldSaveHandler() {

			@Override
			public void onSldSave(SldSaveEvent event) {
				widget.getView().showMessage(MSG.sldSaveMessage());
			}
		});

		SldEditor.getInstance().getEventBus()
				.addHandler(SldCancelEvent.getType(), new SldCancelEvent.SldCancelHandler() {

					@Override
					public void onSldCancel(SldCancelEvent event) {
						widget.getView().showMessage(MSG.sldCancelMessage());
						widget.getView().cancelButtonEvent();
					}
				});

		window.addItem(widget.asWidget());

		window.show();

		return getBackgroundDecoration();

	}

	@Override
	public String getDescription() {
		return SldEditorExample.MSG.sampleDescription();
	}

	@Override
	public String[] getConfigurationFiles() {
		return new String[] {"classpath:org/geomajas/sld/editor/expert/gwt/context/application.xml"};
	}
}


/**
 * Copyright (C) 2014-2019 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.numbercruncher.mathutils;

/**
 * Raise a double value to an integer power.
 */
public final class IntPower
{
  /**
   * Compute and return x^power.
   *
   * @param px
   * @param pexponent
   *        the integer power
   * @return x^power
   */
  public static double raise (final double px, final int pexponent)
  {
    double x = px;
    int exponent = pexponent;
    if (exponent < 0)
      return 1 / raise (x, -exponent);

    double power = 1;

    // Loop to compute x^exponent.
    while (exponent > 0)
    {

      // Is the rightmost exponent bit a 1?
      if ((exponent & 1) == 1)
        power *= x;

      // Square x and shift the exponent 1 bit to the right.
      x *= x;
      exponent >>= 1;
    }

    return power;
  }
}


/**
 * Copyright (C) 2014-2019 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.numbercruncher.matrix;

/**
 * A column vector.
 */
public class ColumnVector extends Matrix
{
  // --------------//
  // Constructors //
  // --------------//

  /**
   * Constructor.
   *
   * @param n
   *        the number of elements
   */
  public ColumnVector (final int n)
  {
    super (n, 1);
  }

  /**
   * Constructor.
   *
   * @param values
   *        the array of values
   */
  public ColumnVector (final float [] values)
  {
    set (values);
  }

  /**
   * Constructor.
   *
   * @param m
   *        the matrix (only the first column used)
   */
  private ColumnVector (final Matrix m)
  {
    _set (m);
  }

  // ---------//
  // Getters //
  // ---------//

  /**
   * Return this column vector's size.
   */
  public int size ()
  {
    return m_nRows;
  }

  /**
   * Return the i'th value of the vector.
   *
   * @param i
   *        the index
   * @return the value
   */
  public float at (final int i)
  {
    return m_aValues[i][0];
  }

  /**
   * Copy the values of this matrix.
   *
   * @return the copied values
   */
  public float [] copyValues1D ()
  {
    final float v[] = new float [m_nRows];

    for (int r = 0; r < m_nRows; ++r)
    {
      v[r] = m_aValues[r][0];
    }

    return v;
  }

  // ---------//
  // Setters //
  // ---------//

  /**
   * Set this column vector from a matrix. Only the first column is used.
   *
   * @param m
   *        the matrix
   */
  private void _set (final Matrix m)
  {
    this.m_nRows = m.m_nRows;
    this.m_nCols = 1;
    this.m_aValues = m.m_aValues;
  }

  /**
   * Set this column vector from an array of values.
   *
   * @param values
   *        the array of values
   */
  protected void set (final float values[])
  {
    this.m_nRows = values.length;
    this.m_nCols = 1;
    this.m_aValues = new float [m_nRows] [1];

    for (int r = 0; r < m_nRows; ++r)
    {
      this.m_aValues[r][0] = values[r];
    }
  }

  /**
   * Set the value of the i'th element.
   *
   * @param i
   *        the index
   * @param value
   *        the value
   */
  public void set (final int i, final float value)
  {
    m_aValues[i][0] = value;
  }

  // -------------------//
  // Vector operations //
  // -------------------//

  /**
   * Add another column vector to this column vector.
   *
   * @param cv
   *        the other column vector
   * @return the sum column vector
   * @throws MatrixException
   *         for invalid size
   */
  public ColumnVector add (final ColumnVector cv) throws MatrixException
  {
    return new ColumnVector (super.add (cv));
  }

  /**
   * Subtract another column vector from this column vector.
   *
   * @param cv
   *        the other column vector
   * @return the sum column vector
   * @throws MatrixException
   *         for invalid size
   */
  public ColumnVector subtract (final ColumnVector cv) throws MatrixException
  {
    return new ColumnVector (super.subtract (cv));
  }

  /**
   * Compute the Euclidean norm.
   *
   * @return the norm
   */
  public float norm ()
  {
    double t = 0;

    for (int r = 0; r < m_nRows; ++r)
    {
      final float v = m_aValues[r][0];
      t += v * v;
    }

    return (float) Math.sqrt (t);
  }

  /**
   * Print the vector values.
   */
  public void print ()
  {
    for (int r = 0; r < m_nRows; ++r)
    {
      System.out.print ("  " + m_aValues[r][0]);
    }
    System.out.println ();
  }
}


/**
 * Copyright (C) 2014-2019 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.numbercruncher.mathutils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;

import javax.annotation.Nonnull;

/**
 * Several useful BigDecimal mathematical functions.
 */
public final class BigFunctions
{
  private BigFunctions ()
  {}

  /**
   * Compute x^exponent to a given scale. Uses the same algorithm as class
   * numbercruncher.mathutils.IntPower.
   *
   * @param px
   *        the value x
   * @param pexponent
   *        the exponent value
   * @param scale
   *        the desired scale of the result
   * @return the result value
   */
  public static BigDecimal intPower (@Nonnull final BigDecimal px, final long pexponent, final int scale)
  {
    BigDecimal x = px;
    long exponent = pexponent;

    // If the exponent is negative, compute 1/(x^-exponent).
    if (exponent < 0)
    {
      return BigDecimal.ONE.divide (intPower (x, -exponent, scale), scale, RoundingMode.HALF_EVEN);
    }

    BigDecimal power = BigDecimal.ONE;

    // Loop to compute value^exponent.
    while (exponent > 0)
    {

      // Is the rightmost bit a 1?
      if ((exponent & 1) == 1)
      {
        power = power.multiply (x).setScale (scale, RoundingMode.HALF_EVEN);
      }

      // Square x and shift exponent 1 bit to the right.
      x = x.multiply (x).setScale (scale, RoundingMode.HALF_EVEN);
      exponent >>= 1;

      Thread.yield ();
    }

    return power;
  }

  /**
   * Compute the integral root of x to a given scale, x ≥ 0. Use Newton's
   * algorithm.
   *
   * @param px
   *        the value of x
   * @param index
   *        the integral root value
   * @param scale
   *        the desired scale of the result
   * @return the result value
   */
  public static BigDecimal intRoot (@Nonnull final BigDecimal px, final long index, final int scale)
  {
    BigDecimal x = px;

    // Check that x >= 0.
    if (x.signum () < 0)
    {
      throw new IllegalArgumentException ("x < 0: " + x);
    }

    final int sp1 = scale + 1;
    final BigDecimal n = x;
    final BigDecimal i = BigDecimal.valueOf (index);
    final BigDecimal im1 = BigDecimal.valueOf (index - 1);
    final BigDecimal tolerance = BigDecimal.valueOf (5).movePointLeft (sp1);
    BigDecimal xPrev;

    // The initial approximation is x/index.
    x = x.divide (i, scale, RoundingMode.HALF_EVEN);

    // Loop until the approximations converge
    // (two successive approximations are equal after rounding).
    do
    {
      // x^(index-1)
      final BigDecimal xToIm1 = intPower (x, index - 1, sp1);

      // x^index
      final BigDecimal xToI = x.multiply (xToIm1).setScale (sp1, RoundingMode.HALF_EVEN);

      // n + (index-1)*(x^index)
      final BigDecimal numerator = n.add (im1.multiply (xToI)).setScale (sp1, RoundingMode.HALF_EVEN);

      // (index*(x^(index-1))
      final BigDecimal denominator = i.multiply (xToIm1).setScale (sp1, RoundingMode.HALF_EVEN);

      // x = (n + (index-1)*(x^index)) / (index*(x^(index-1)))
      xPrev = x;
      x = numerator.divide (denominator, sp1, RoundingMode.DOWN);

      Thread.yield ();
    } while (x.subtract (xPrev).abs ().compareTo (tolerance) > 0);

    return x;
  }

  /**
   * Compute e^x to a given scale. Break x into its whole and fraction parts and
   * compute (e^(1 + fraction/whole))^whole using Taylor's formula.
   *
   * @param x
   *        the value of x
   * @param scale
   *        the desired scale of the result
   * @return the result value
   */
  public static BigDecimal exp (final BigDecimal x, final int scale)
  {
    // e^0 = 1
    if (x.signum () == 0)
    {
      return BigDecimal.ONE;
    }

    // If x is negative, return 1/(e^-x).
    if (x.signum () == -1)
    {
      return BigDecimal.ONE.divide (exp (x.negate (), scale), scale, RoundingMode.HALF_EVEN);
    }

    // Compute the whole part of x.
    BigDecimal xWhole = x.setScale (0, RoundingMode.DOWN);

    // If there isn't a whole part, compute and return e^x.
    if (xWhole.signum () == 0)
      return _expTaylor (x, scale);

    // Compute the fraction part of x.
    final BigDecimal xFraction = x.subtract (xWhole);

    // z = 1 + fraction/whole
    final BigDecimal z = BigDecimal.ONE.add (xFraction.divide (xWhole, scale, RoundingMode.HALF_EVEN));

    // t = e^z
    final BigDecimal t = _expTaylor (z, scale);

    final BigDecimal maxLong = BigDecimal.valueOf (Long.MAX_VALUE);
    BigDecimal result = BigDecimal.ONE;

    // Compute and return t^whole using intPower().
    // If whole > Long.MAX_VALUE, then first compute products
    // of e^Long.MAX_VALUE.
    while (xWhole.compareTo (maxLong) >= 0)
    {
      result = result.multiply (intPower (t, Long.MAX_VALUE, scale)).setScale (scale, RoundingMode.HALF_EVEN);
      xWhole = xWhole.subtract (maxLong);

      Thread.yield ();
    }
    return result.multiply (intPower (t, xWhole.longValue (), scale)).setScale (scale, RoundingMode.HALF_EVEN);
  }

  /**
   * Compute e^x to a given scale by the Taylor series.
   *
   * @param x
   *        the value of x
   * @param scale
   *        the desired scale of the result
   * @return the result value
   */
  private static BigDecimal _expTaylor (final BigDecimal x, final int scale)
  {
    BigDecimal factorial = BigDecimal.ONE;
    BigDecimal xPower = x;
    BigDecimal sumPrev;

    // 1 + x
    BigDecimal sum = x.add (BigDecimal.ONE);

    // Loop until the sums converge
    // (two successive sums are equal after rounding).
    int i = 2;
    do
    {
      // x^i
      xPower = xPower.multiply (x).setScale (scale, RoundingMode.HALF_EVEN);

      // i!
      factorial = factorial.multiply (BigDecimal.valueOf (i));

      // x^i/i!
      final BigDecimal term = xPower.divide (factorial, scale, RoundingMode.HALF_EVEN);

      // sum = sum + x^i/i!
      sumPrev = sum;
      sum = sum.add (term);

      ++i;
      Thread.yield ();
    } while (sum.compareTo (sumPrev) != 0);

    return sum;
  }

  /**
   * Compute the natural logarithm of x to a given scale, x > 0.
   *
   * @param x
   *        Base value
   * @param scale
   *        Scale
   * @return ln(x)
   */
  public static BigDecimal ln (@Nonnull final BigDecimal x, final int scale)
  {
    // Check that x > 0.
    if (x.signum () <= 0)
    {
      throw new IllegalArgumentException ("x <= 0: " + x);
    }

    // The number of digits to the left of the decimal point.
    final int magnitude = x.toString ().length () - x.scale () - 1;

    if (magnitude < 3)
    {
      return _lnNewton (x, scale);
    }

    // Compute magnitude*ln(x^(1/magnitude)).

    // x^(1/magnitude)
    final BigDecimal root = intRoot (x, magnitude, scale);

    // ln(x^(1/magnitude))
    final BigDecimal lnRoot = _lnNewton (root, scale);

    // magnitude*ln(x^(1/magnitude))
    return BigDecimal.valueOf (magnitude).multiply (lnRoot).setScale (scale, RoundingMode.HALF_EVEN);
  }

  /**
   * Compute the natural logarithm of x to a given scale, x > 0. Use Newton's
   * algorithm.
   */
  private static BigDecimal _lnNewton (@Nonnull final BigDecimal px, final int scale)
  {
    BigDecimal x = px;
    final int sp1 = scale + 1;
    final BigDecimal n = x;
    BigDecimal term;

    // Convergence tolerance = 5*(10^-(scale+1))
    final BigDecimal tolerance = BigDecimal.valueOf (5).movePointLeft (sp1);

    // Loop until the approximations converge
    // (two successive approximations are within the tolerance).
    do
    {

      // e^x
      final BigDecimal eToX = exp (x, sp1);

      // (e^x - n)/e^x
      term = eToX.subtract (n).divide (eToX, sp1, RoundingMode.DOWN);

      // x - (e^x - n)/e^x
      x = x.subtract (term);

      Thread.yield ();
    } while (term.compareTo (tolerance) > 0);

    return x.setScale (scale, RoundingMode.HALF_EVEN);
  }

  /**
   * Compute the arctangent of x to a given scale, |x| < 1
   *
   * @param x
   *        the value of x
   * @param scale
   *        the desired scale of the result
   * @return the result value
   */
  public static BigDecimal arctan (@Nonnull final BigDecimal x, final int scale)
  {
    // Check that |x| < 1.
    if (x.abs ().compareTo (BigDecimal.ONE) >= 0)
    {
      throw new IllegalArgumentException ("|x| >= 1: " + x);
    }

    // If x is negative, return -arctan(-x).
    if (x.signum () == -1)
    {
      return arctan (x.negate (), scale).negate ();
    }
    return _arctanTaylor (x, scale);
  }

  /**
   * Compute the arctangent of x to a given scale by the Taylor series, |x| < 1
   *
   * @param x
   *        the value of x
   * @param scale
   *        the desired scale of the result
   * @return the result value
   */
  private static BigDecimal _arctanTaylor (final BigDecimal x, final int scale)
  {
    final int sp1 = scale + 1;
    int i = 3;
    boolean addFlag = false;

    BigDecimal power = x;
    BigDecimal sum = x;
    BigDecimal term;

    // Convergence tolerance = 5*(10^-(scale+1))
    final BigDecimal tolerance = BigDecimal.valueOf (5).movePointLeft (sp1);

    // Loop until the approximations converge
    // (two successive approximations are within the tolerance).
    do
    {
      // x^i
      power = power.multiply (x).multiply (x).setScale (sp1, RoundingMode.HALF_EVEN);

      // (x^i)/i
      term = power.divide (BigDecimal.valueOf (i), sp1, RoundingMode.HALF_EVEN);

      // sum = sum +- (x^i)/i
      sum = addFlag ? sum.add (term) : sum.subtract (term);

      i += 2;
      addFlag = !addFlag;

      Thread.yield ();
    } while (term.compareTo (tolerance) > 0);

    return sum;
  }

  /**
   * Compute the square root of x to a given scale, x ≥ 0. Use Newton's
   * algorithm.
   *
   * @param x
   *        the value of x
   * @param scale
   *        the desired scale of the result
   * @return the result value
   */
  public static BigDecimal sqrt (@Nonnull final BigDecimal x, final int scale)
  {
    // Check that x >= 0.
    if (x.signum () < 0)
    {
      throw new IllegalArgumentException ("x < 0: " + x);
    }

    // n = x*(10^(2*scale))
    final BigInteger n = x.movePointRight (scale << 1).toBigInteger ();

    // The first approximation is the upper half of n.
    final int bits = (n.bitLength () + 1) >> 1;
    BigInteger ix = n.shiftRight (bits);
    BigInteger ixPrev;

    // Loop until the approximations converge
    // (two successive approximations are equal after rounding).
    do
    {
      ixPrev = ix;

      // x = (x + n/x)/2
      ix = ix.add (n.divide (ix)).shiftRight (1);

      Thread.yield ();
    } while (ix.compareTo (ixPrev) != 0);

    return new BigDecimal (ix, scale);
  }
}


/**
 * Copyright (C) 2014-2019 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.numbercruncher.piutils;

import java.time.Duration;
import java.time.LocalDateTime;

import com.helger.commons.datetime.PDTFactory;

/**
 * Utility class for programs that compute pi.
 */
public abstract class AbstractPiFormula
{
  /**
   * Print the string containing the digits of pi.
   *
   * @param piString
   *        the string containing the digits of pi
   */
  protected void printPi (final String piString)
  {
    System.out.print ("\npi = " + piString.substring (0, 2));

    int index = 2;
    int line = 0;
    int group = 0;
    final int length = piString.length ();

    // Loop for each group of 5 digits
    while (index + 5 < length)
    {
      System.out.print (piString.substring (index, index + 5) + " ");
      index += 5;

      // End of line after 10 groups.
      if (++group == 10)
      {
        System.out.println ();

        // Print a blank line after 10 lines.
        if (++line == 10)
        {
          System.out.println ();
          line = 0;
        }

        System.out.print ("       ");
        group = 0;
      }
    }

    // Print the last partial line.
    if (index < length)
    {
      System.out.println (piString.substring (index));
    }
  }

  /**
   * Return a timestamp string that contains the elapsed time period.
   *
   * @param time
   *        the starting time of the period
   * @return the timestamp string
   */
  protected String timestamp (final long time)
  {
    // Current time followed by elapsed time as (hh:mm:ss).
    final LocalDateTime aLDT = PDTFactory.getCurrentLocalDateTime ();
    final LocalDateTime aOld = PDTFactory.createLocalDateTime (time);

    return aLDT.toLocalTime ().toString () + " (" + Duration.between (aOld, aLDT).toString () + ")";
  }
}