package de.saxsys.mvvmfx;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.ResourceBundle;

import de.saxsys.mvvmfx.internal.viewloader.FxmlViewLoader;
import de.saxsys.mvvmfx.internal.viewloader.JavaViewLoader;
import de.saxsys.mvvmfx.internal.viewloader.ResourceBundleManager;
import javafx.util.BuilderFactory;

/**
 * Fluent API for loading Views.

 *
 * The typical usage will look like this:
 *
 *

 *
 *

 * public class MyCoolViewModel implements ViewModel {...}
 * public class MyCoolView implements FxmlView{@code <MyCoolViewModel>} {...}
 *
 *
 * public class MyApp extends Application {
 *
 *    {@literal @}Override
 *     public void start(Stage stage) {
 * 		ViewTuple{@code <MyCoolView, MyCoolViewModel>} viewTuple
 * 				= FluentViewLoader.fxmlView(MyCoolView.class).load();
 *
 * 		Parent root = viewTuple.getView();
 * 		stage.setScene(new Scene(root));
 * 		stage.show();
 *     }
 * }
 *
 *

 *
 *
 * This class is implemented as a Step-Builder. You can choose between
 * {@link FxmlView} and {@link JavaView} with the first method call. After that
 * you will only get builder-methods that are suitable for the view type you
 * have chosen.
 *
 * @author manuel.mauky
 */
public class FluentViewLoader {

    /**
     * This class is the builder step to load a java based view. It is accessed
     * from the {@link FluentViewLoader} with the method
     * {@link FluentViewLoader#javaView(Class)}.
     *
     * @param <ViewType>
     *            the generic type of the View that should be loaded. This type
     *            has to implement {@link de.saxsys.mvvmfx.JavaView}.
     * @param <ViewModelType>
     *            the generic type of the ViewModel. This type has to implement
     *            {@link de.saxsys.mvvmfx.ViewModel}.
     */
    public static class JavaViewStep<ViewType extends JavaView<? extends ViewModelType>, ViewModelType extends ViewModel> {

        private final Class<? extends ViewType> viewType;
		private List<ResourceBundle> resourceBundles;

        private ViewModelType viewModel;
        private ViewType codeBehind;
        private Context context;
        private Collection<Scope> providedScopes;

        JavaViewStep(Class<? extends ViewType> viewType) {
            this.viewType = viewType;
        }

        public JavaViewStep<ViewType, ViewModelType> context(Context context) {
            this.context = context;
            return this;
        }

        public JavaViewStep<ViewType, ViewModelType> providedScopes(Scope... providedScopes) {
            this.providedScopes = Arrays.asList(providedScopes);
            return this;
        }

        public JavaViewStep<ViewType, ViewModelType> providedScopes(Collection<Scope> providedScopes) {
            this.providedScopes = providedScopes;
            return this;
        }

        /**
         * Provide a {@link ResourceBundle} that is used while loading this
         * view. Note: It is possible to provide a global application-wide
         * resourceBundle via
         * {@link MvvmFX#setGlobalResourceBundle(ResourceBundle)} method.
         *

         * If there is a global resourceBundle set it will be merged with the
         * resourceBundle provided by this builder method. The resourceBundle
         * provided by this method will have a higher priority then the global
         * one which means that if there are duplicate keys, the values of the
         * global resourceBundle will be overwritten and the values of this
         * resourceBundle will be used.
		 *

		 * It is possible to add multiple resourceBundles by invoking this builder method
		 * multiple times. In this case the last provided resourceBundle will have the
		 * highest priority when it comes to overwriting values with the same keys.
         *
         * @param resourceBundle
         *            the resource bundle that is used while loading the view.
         * @return this instance of the builder step.
         */
        public JavaViewStep<ViewType, ViewModelType> resourceBundle(ResourceBundle resourceBundle) {
			if(resourceBundles == null) {
				resourceBundles = new ArrayList<>();
			}
			resourceBundles.add(resourceBundle);
            return this;
        }

        /**
         * This param is used to define an existing viewModel instance to be
         * used when loading the view.

         *
         * A typical use case is when you like to have two or more views that
         * are sharing the same viewModel.
         *
         * @param viewModel
         *            the viewModel instance that is used to load the java view.
         * @return this instance of the builder step.
         */
        public JavaViewStep<ViewType, ViewModelType> viewModel(ViewModelType viewModel) {
            this.viewModel = viewModel;
            return this;
        }

        /**
         * This param is used to define an existing instance of the codeBehind
         * class that is used instead of creating a new one while loading.

         *
         * This can be useful when creating custom controls.
         *
         * @param codeBehind
         *            the codeBehind instance that is used to load this java
         *            view.
         * @return this instance of the builder step.
         */
        public JavaViewStep<ViewType, ViewModelType> codeBehind(ViewType codeBehind) {
            this.codeBehind = codeBehind;
            return this;
        }

        /**
         * The final step of the Fluent API. This method loads the view based on
         * the given params.
         *
         * @return a view tuple containing the loaded view.
         */
        public ViewTuple<ViewType, ViewModelType> load() {
            JavaViewLoader javaViewLoader = new JavaViewLoader();

			final ResourceBundle bundle = ResourceBundleManager.getInstance().mergeListWithGlobal(resourceBundles);

			return javaViewLoader.loadJavaViewTuple(viewType, bundle, viewModel, codeBehind, context,
					providedScopes);
		}
	}

    /**
     * This class is the builder step to load a fxml based view. It is accessed
     * from the {@link FluentViewLoader} with the method
     * {@link FluentViewLoader#fxmlView(Class)}.
     *
     * @param <ViewType>
     *            the generic type of the View that should be loaded. This type
     *            has to implement {@link de.saxsys.mvvmfx.FxmlView}.
     * @param <ViewModelType>
     *            the generic type of the ViewModel. This type has to implement
     *            {@link de.saxsys.mvvmfx.ViewModel}.
     */
    public static class FxmlViewStep<ViewType extends FxmlView<? extends ViewModelType>, ViewModelType extends ViewModel> {

        private final Class<? extends ViewType> viewType;
        private List<ResourceBundle> resourceBundles;
        private Object root;
        private ViewType codeBehind;
        private ViewModelType viewModel;
        private Context context;
        private Collection<Scope> providedScopes;

        private List<BuilderFactory> builderFactories;

        FxmlViewStep(Class<? extends ViewType> viewType) {
            this.viewType = viewType;
        }

        public FxmlViewStep<ViewType, ViewModelType> context(Context context) {
            this.context = context;
            return this;
        }

        public FxmlViewStep<ViewType, ViewModelType> providedScopes(Scope... providedScopes) {

            // TODO: add scopes instead of reinitialization
            this.providedScopes = Arrays.asList(providedScopes);
            return this;
        }

        public FxmlViewStep<ViewType, ViewModelType> providedScopes(Collection<Scope> providedScopes) {
            this.providedScopes = providedScopes;
            return this;
        }

        /**
         * Provide a {@link ResourceBundle} that is used while loading this
         * view. Note: It is possible to provide a global application-wide
         * resourceBundle via
         * {@link MvvmFX#setGlobalResourceBundle(ResourceBundle)} method.
         *

         * If there is a global resourceBundle set it will be merged with the
         * resourceBundle provided by this builder method. The resourceBundle
         * provided by this method will have a higher priority then the global
         * one which means that if there are duplicate keys, the values of the
         * global resourceBundle will be overwritten and the values of this
         * resourceBundle will be used.
		 *

		 * It is possible to add multiple resourceBundles by invoking this builder method
		 * multiple times. In this case the last provided resourceBundle will have the
		 * highest priority when it comes to overwriting values with the same keys.
         *
         * @param resourceBundle
         *            the resource bundle that is used while loading the view.
         * @return this instance of the builder step.
         */
        public FxmlViewStep<ViewType, ViewModelType> resourceBundle(ResourceBundle resourceBundle) {
        	if(resourceBundles == null) {
        		resourceBundles = new ArrayList<>();
			}
			resourceBundles.add(resourceBundle);
            return this;
        }

        /**
         * This param is used to define a JavaFX node that is used as the root
         * element when loading the fxml file.

         *
         * This can be useful when creating custom controls with the fx:root
         * element.
         *
         * @param root
         *            the root element that is used to load the fxml file.
         * @return this instance of the builder step.
         */
        public FxmlViewStep<ViewType, ViewModelType> root(Object root) {
            this.root = root;
            return this;
        }

        /**
         * This param is used to define an existing instance of the codeBehind
         * class that is used instead of creating a new one while loading.

         *
         * This can be useful when creating custom controls with the fx:root
         * element.
         *
         * @param codeBehind
         *            the codeBehind instance that is used to load the fxml
         *            file.
         * @return this instance of the builder step.
         */
        public FxmlViewStep<ViewType, ViewModelType> codeBehind(ViewType codeBehind) {
            this.codeBehind = codeBehind;
            return this;
        }

        /**
         * This param is used to define an existing viewModel instance to be
         * used when loading the view.

         *
         * A typical use case is when you like to have two or more views that
         * are sharing the same viewModel.
         *
         * @param viewModel
         *            the viewModel instance that is used to load the fxml file.
         * @return this instance of the builder step.
         */
        public FxmlViewStep<ViewType, ViewModelType> viewModel(ViewModelType viewModel) {
            this.viewModel = viewModel;
            return this;
        }

		/**
		 * This param is used to add a {@link BuilderFactory} that is used when loading the view.
.
		 * MvvmFX supports multiple builder factories. There are two ways of defining builder factories:
		 *

		 *
a local builder factory by using this method.
		 *     In this case the builder factory is only used for this loading procedure.

		 *
a global builder factory by using {@link MvvmFX#addGlobalBuilderFactory(BuilderFactory)}.
		 *     This defines a global builder factory that is used for all loading procedures.

		 *

		 *

		 * For most use cases it's better to define a global builder factory.
		 * Only if you like to limit the usage of the
		 * builder factory to only this loading procedure then use this fluent API method instead.
		 *
		 * @param builderFactory a builder factory that is used only for this loading procedure.
		 *
		 * @return this instance of the builder step.
		 */
		public FxmlViewStep<ViewType, ViewModelType> builderFactory(BuilderFactory builderFactory) {
        	if(this.builderFactories == null) {
        		this.builderFactories = new ArrayList<>();
			}

			this.builderFactories.add(builderFactory);

        	return this;
		}

        /**
         * The final step of the Fluent API. This method loads the view based on
         * the given params.
         *
         * @return a view tuple containing the loaded view.
         */
        public ViewTuple<ViewType, ViewModelType> load() {
            FxmlViewLoader fxmlViewLoader = new FxmlViewLoader();

			final ResourceBundle bundle = ResourceBundleManager.getInstance().mergeListWithGlobal(resourceBundles);

			return fxmlViewLoader.loadFxmlViewTuple(viewType, bundle, codeBehind, root, viewModel,
					context, providedScopes, builderFactories);
		}
    }

    /**
     * This method is the entry point of the Fluent API to load a java based
     * view.
     *
     * @param viewType
     *            the type of the view that should be loaded.
     * @param <ViewType>
     *            the type of the View that should be loaded. This type has to
     *            implement {@link de.saxsys.mvvmfx.JavaView}.
     * @param <ViewModelType>
     *            the type of the ViewModel. This type has to implement
     *            {@link de.saxsys.mvvmfx.ViewModel}.
     *
     * @return a builder step that can be further configured and then load the
     *         actual view.
     */
    public static <ViewType extends JavaView<? extends ViewModelType>, ViewModelType extends ViewModel> JavaViewStep<ViewType, ViewModelType> javaView(
            Class<? extends ViewType> viewType) {
        return new JavaViewStep<>(viewType);
    }

    /**
     * This method is the entry point of the Fluent API to load a fxml based
     * View.
     *
     * @param viewType
     *            the type of the view that should be loaded.
     * @param <ViewType>
     *            the generic type of the View that should be loaded. This type
     *            has to implement {@link de.saxsys.mvvmfx.FxmlView}.
     * @param <ViewModelType>
     *            the generic type of the ViewModel. This type has to implement
     *            {@link de.saxsys.mvvmfx.ViewModel}.
     *
     * @return a builder step that can be further configured and then load the
     *         actual view.
     */
    public static <ViewType extends FxmlView<? extends ViewModelType>, ViewModelType extends ViewModel> FxmlViewStep<ViewType, ViewModelType> fxmlView(
            Class<? extends ViewType> viewType) {
        return new FxmlViewStep<>(viewType);
    }

}


/*******************************************************************************
 * Copyright 2013 manuel.mauky, alexander.casall
 *
 * 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 de.saxsys.mvvmfx.utils.listener;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.BiConsumer;

/**
 * The {@link ListenerManager} is used to be able to clean up listeners when they are not used anymore. A typical use
 * case is a component that registers some listeners to its properties. When the component isn't used anymore the
 * listeners needs to be removed from the properties. Otherwise it could lead to memory leaks as the garbage collector
 * can't delete the objects as there are still references available.
 *
 * With the {@link ListenerManager} you can reduce the possibility to forget a listener because all listeners can be
 * removed with one single method {@link ListenerManager#clean()}.
 *
 * @author manuel.mauky
 *
 */
public class ListenerManager implements ICleanable {

	@SuppressWarnings("rawtypes")
	private final Map<ObservableValue<?>, Set<ChangeListener>> simpleChangeListeners = new WeakHashMap<>();
	@SuppressWarnings("rawtypes")
	private final Map<ObservableList<?>, Set<ListChangeListener>> listChangeListeners = new WeakHashMap<>();

	private final Map<Observable, Set<InvalidationListener>> invalidationListeners = new WeakHashMap<>();

	/**
	 * Register the given {@link ChangeListener} to the {@link ObservableValue}. The listener is added to the observable
	 * and will be added for management so it can be cleaned up with the {@link #clean()} method.
	 *
	 * @param observable
	 *            the observable that the listener is added to.
	 * @param listener
	 *            the listener that is registered.
	 * @param <T>
	 *            the generic type of the observable value.
	 */
	public <T> void register(ObservableValue<T> observable, ChangeListener<? super T> listener) {
		if (!simpleChangeListeners.containsKey(observable)) {
			this.simpleChangeListeners.put(observable, Collections.newSetFromMap(new WeakHashMap<>()));
		}

		Set<ChangeListener> observers = this.simpleChangeListeners.get(observable);
		observers.add(listener);
		observable.addListener(listener);
	}

	/**
	 * Register the given {@link ListChangeListener} to the {@link ObservableList}. The listener is added to the
	 * observable and will be added for management so it can be cleaned up with the {@link #clean()} method.
	 *
	 * @param observable
	 *            the observable list that the listener is added to.
	 * @param listener
	 *            the listener that is registered.
	 * @param <T>
	 *            the generic type of the observable list.
	 */
	public <T> void register(ObservableList<T> observable, ListChangeListener<? super T> listener) {
		if (!listChangeListeners.containsKey(observable)) {
			this.listChangeListeners.put(observable, Collections.newSetFromMap(new WeakHashMap<>()));
		}

		Set<ListChangeListener> observers = this.listChangeListeners.get(observable);
		observers.add(listener);
		observable.addListener(listener);
	}

	/**
	 * Register the given {@link InvalidationListener} to the {@link Observable} . The listener is added to the
	 * observable and will be added for management so it can be cleaned up with the {@link #clean()} method.
	 *
	 * @param observable
	 *            the observable that the listener is added to.
	 * @param listener
	 *            the listener that is registered.
	 */
	public void register(Observable observable, InvalidationListener listener) {
		if (!invalidationListeners.containsKey(observable)) {
			this.invalidationListeners.put(observable, Collections.newSetFromMap(new WeakHashMap<>()));
		}

		Set<InvalidationListener> observers = this.invalidationListeners.get(observable);
		observers.add(listener);
		observable.addListener(listener);
	}

	@Override
	public void clean() {
		clearMap(simpleChangeListeners, (observable, listener) -> observable.removeListener(listener));
		clearMap(listChangeListeners, (observable, listener) -> observable.removeListener(listener));
		clearMap(invalidationListeners, (observable, listener) -> observable.removeListener(listener));
	}

	/**
	 * This method is used to clear the given map. To do this you need to implement a BiConsumer that calls the specific
	 * method to remove a listener from an observable.
	 *
	 * This needs to be done as there is no common interface with a remove method that all types of observables are
	 * implementing. Therefore the method call to the specific removeListener method needs to be done in an extra
	 * function.
	 *
	 * @param map
	 *            the multimap that contains the observables and listeners.
	 * @param consumer
	 *            a function that calls the specific remove method for the given types.
	 */
	private <T, U> void clearMap(Map<T, Set<U>> map, BiConsumer<T, U> consumer) {
		for (T observable : map.keySet()) {
			for (U listener : map.get(observable)) {
				consumer.accept(observable, listener);
			}
		}
		map.clear();
	}
}


/*******************************************************************************
 * Copyright 2013 Alexander Casall, Manuel Mauky
 *
 * 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 de.saxsys.mvvmfx.cdi;

import de.saxsys.mvvmfx.internal.MvvmfxApplication;
import javafx.application.Application;
import javafx.stage.Stage;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.se.SeContainerInitializer;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.inject.Inject;

import de.saxsys.mvvmfx.MvvmFX;
import de.saxsys.mvvmfx.cdi.internal.MvvmfxProducer;

/**
 * This class has to be extended by the user to build a javafx application powered by CDI.
 *
 * @author manuel.mauky
 */
public abstract class MvvmfxCdiApplication extends Application implements MvvmfxApplication {

	private final BeanManager beanManager;
	private CreationalContext<MvvmfxCdiApplication> ctx;
    private InjectionTarget<MvvmfxCdiApplication> injectionTarget;
    private final SeContainer container;

    @Inject
	private MvvmfxProducer producer;

    public MvvmfxCdiApplication() {
        container = SeContainerInitializer
                .newInstance()
                .initialize();

		MvvmFX.setCustomDependencyInjector((type) -> container.select(type).get());

		MvvmfxProducer mvvmfxProducer = container.select(MvvmfxProducer.class).get();
		mvvmfxProducer.setHostServices(getHostServices());

		beanManager = container.getBeanManager();
	}

	/**
	 * This method is overridden to initialize the mvvmFX framework. Override the
	 * {@link #startMvvmfx(javafx.stage.Stage)} method for your application entry point and startup code instead of this
	 * method.
	 */
	@Override
	public final void start(Stage primaryStage) throws Exception {
		producer.setPrimaryStage(primaryStage);

		startMvvmfx(primaryStage);
	}

	/**
	 * This method is called when the javafx application is initialized. See
	 * {@link javafx.application.Application#init()} for more details.
	 *
	 * Unlike the original init method in {@link javafx.application.Application} this method contains logic to
	 * initialize the CDI container.  For this reason this method is now final to prevent unintended overriding.
	 * 	 * Please use {@link #initMvvmfx()} for you own initialization logic.
	 *
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	@Override
	public final void init() throws Exception {
		ctx = beanManager.createCreationalContext(null);
		injectionTarget = beanManager.createInjectionTarget(
				beanManager.createAnnotatedType((Class<MvvmfxCdiApplication>) this.getClass()));

		injectionTarget.inject(this, ctx);
		injectionTarget.postConstruct(this);

		producer.setApplicationParameters(getParameters());

		initMvvmfx();
	}

	/**
	 * This method is called when the application should stop. See {@link javafx.application.Application#stop()} for
	 * more details.
	 *
	 * Unlike the original stop method in {@link javafx.application.Application} this method contains logic to release
	 * resources managed by the CDI container.  For this reason this method is now final to prevent unintended overriding.
	 * 	 * Please use {@link #stopMvvmfx()} ()} for you own initialization logic.
	 *
	 * @throws Exception
	 */
	@Override
	public final void stop() throws Exception {
		stopMvvmfx();

		injectionTarget.preDestroy(this);
		injectionTarget.dispose(this);

		ctx.release();

		container.close();
	}
}


/*******************************************************************************
 * Copyright 2013 Alexander Casall
 *
 * 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 de.saxsys.mvvmfx.utils.itemlist;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListView;
import javafx.scene.control.SingleSelectionModel;

import java.util.List;

/**
 * Element that you can use in a View Model to transform any list to a string representation which can be bound to UI
 * Elements like {@link ChoiceBox} or {@link ListView}. <b>You should only expose the {@link #stringListProperty()}
 * and/or the {@link #selectedIndexProperty()} to the view, otherwise you create a visibility of the view to the model.
 * If you want to expose it more convenient, use the {@link SelectableStringList} interface to hide all dependencies to
 * the model. Create something like this in your View Model:
 *
 * <code>
 * public SelectableStringList stringListProperty(){
 * 		return selectableItemListInstance;
 * }
 * </code>
 *
 * </b> You have to provide a {@link ModelToStringFunction} to define how to map from your model representation to a
 * string. In addition you have properties which represents the actual selection state of a list. You can set either the
 * {@link #selectedIndexProperty()} or the {@link #selectedItemProperty()} and the other will change automatically.
 *
 * @author sialcasa
 *
 * @param <ListType>
 *            type of the list elements which should be transformed to a string list
 */
public class SelectableItemList<ListType> extends ItemList<ListType> implements
		SelectableStringList {

	// Indeces
	private SingleSelectionModel<ListType> selectionModel = new SingleSelectionModel<ListType>() {
		@Override
		protected int getItemCount() {
			return modelListProperty().size();
		}

		@Override
		protected ListType getModelItem(int index) {
			return index == -1 ? null : modelListProperty().get(index);
		}
	};

	private ObjectProperty<ListType> selectedItem = new SimpleObjectProperty<>();

	/**
	 * Creates a {@link SelectableItemList} by a given list of items and a {@link ModelToStringFunction}.
	 *
	 * @param itemList
	 *            which should be transformed for the UI
	 * @param modelToStringMapper
	 *            which is used for transformation
	 */
	public SelectableItemList(ObservableList<ListType> itemList,
			final ModelToStringFunction<ListType> modelToStringMapper) {
		super(itemList, modelToStringMapper);
		// Order of processing is important!

		selectedItem.set(null);
		createIndexEvents();
	}

	// When the index property changed we have to change the selected item too
	// When the selected item changed we want to set the index property too
	private void createIndexEvents() {
		selectionModel
                .selectedIndexProperty()
				.addListener((bean, oldVal, newVal) -> {
                    int index = newVal.intValue();
                    ListType item = index == -1 ? null : modelListProperty()
                            .get(index);
                    selectedItem.set(item);
                });

		selectedItem.addListener((observable, oldVal, newVal) -> {

            // Item null
            if (newVal == null) {
                selectionModel.select(-1);
                selectedItem.set(null);

            } else {
                int index = modelListProperty().get().indexOf(newVal);
                // Item not found
                if (index != -1) {
                    selectionModel.select(index);
                } else {
                    // If item not found - Rollback
                    selectedItem.set(oldVal);
                }
            }

        });
	}

	/**
	 * Represents an {@link Integer} which is the current selection index. If you set another value to the property
	 * {@link #selectedItemProperty()} will change automatically. If the value was an invalid index, the change will be
	 * rollbacked.
	 *
	 * @return the index property
	 */
	@Override
	public ReadOnlyIntegerProperty selectedIndexProperty() {
		return this.selectionModel.selectedIndexProperty();
	}

	@Override
	public int getSelectedIndex() {
		return selectedIndexProperty().get();
	}

	/**
	 * @return String representation of {@link #modelListProperty()}.
	 */
	@Override
	public ReadOnlyListProperty<String> stringListProperty() {
		return targetListProperty();
	}

	/**
	 * @return String representation of {@link #modelListProperty()} as List.
	 */
	public List<String> getStringList() {
		return targetListProperty().get();
	}

	/**
	 * Represents the current selected item. If you set another value to the property {@link #selectedIndexProperty()}
	 * will change automatically. If the value was an invalid item which is not in the itemlist, the change will be
	 * rollbacked.
	 *
	 * @return the item property
	 */
	public ObjectProperty<ListType> selectedItemProperty() {
		return this.selectedItem;
	}

	/**
	 * @param item
	 *            the item will be selected.
	 *
	 * @see #selectedItemProperty()
	 */
	public void select(ListType item) {
		this.selectedItem.set(item);
	}

	/**
	 * @return the currently selected item.
	 *
	 * @see #selectedItemProperty()
	 */
	public ListType getSelectedItem() {
		return this.selectedItem.get();
	}

	@Override
	public void clearSelection() {
		this.selectionModel.clearSelection();
	}

	@Override
	public void select(int index) {
		this.selectionModel.select(index);
	}

}


/*******************************************************************************
 * Copyright 2015 Alexander Casall, Manuel Mauky
 *
 * 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 de.saxsys.mvvmfx.internal.viewloader;

import de.saxsys.mvvmfx.internal.SideEffectWithException;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;

/**
 * This class is used to encapsulate operations for the handling of reflection.
 *
 * @author manuel.mauky
 */
public class ReflectionUtils {

	/**
	 * Returns all fields with the given annotation. Only fields that are declared in the actual class of the instance
	 * are considered (i.e. no fields from super classes). This includes private fields.
	 *
	 * @param target
	 *            the instance that's class is used to find annotations.
	 * @param annotationType
	 *            the type of the annotation that is searched for.
	 * @return a List of Fields that are annotated with the given annotation.
	 */
	public static List<Field> getFieldsWithAnnotation(Object target, Class<? extends Annotation> annotationType) {
		return ReflectionUtils.getFieldsFromClassHierarchy(target.getClass())
                .stream()
				.filter(field -> field.isAnnotationPresent(annotationType))
				.collect(Collectors.toList());
	}

	/**
	 * Returns all fields of the given type and all parent types (except Object).
	 *

	 * The difference to {@link Class#getFields()} is that getFields only returns public fields while this method will
	 * return all fields whatever the access modifier is.
	 *

     *
	 * The difference to {@link Class#getDeclaredFields()} is that getDeclaredFields returns all fields (with all
	 * modifiers) from the given class but not from super classes. This method instead will return all fields of all
	 * modifiers from all super classes up in the class hierarchy, except from Object itself.
	 *
	 * @param type the type whose fields will be searched.
	 * @return a list of field instances.
	 */
	public static List<Field> getFieldsFromClassHierarchy(Class<?> type) {

		final List<Field> classFields = new ArrayList<>();
		classFields.addAll(Arrays.asList(type.getDeclaredFields()));
		final Class<?> parentClass = type.getSuperclass();

		if (parentClass != null && !(parentClass.equals(Object.class))) {
			List<Field> parentClassFields = getFieldsFromClassHierarchy(parentClass);
			classFields.addAll(parentClassFields);
		}

		return classFields;
	}

	/**
	 * Helper method to execute a callback on a given member. This method encapsulates the error handling logic and the
	 * handling of accessibility of the member.
	 *
	 * After the callback is executed the accessibility of the member will be reset to the originally state.
	 *
	 * @param member
	 *            the member that is made accessible to run the callback
	 * @param callable
	 *            the callback that will be executed.
	 * @param errorMessage
	 *            the error message that is used in the exception when something went wrong.
	 *
	 * @return the return value of the given callback.
	 *
	 * @throws IllegalStateException
	 *             when something went wrong.
	 */
	public static <T> T accessMember(final AccessibleObject member, final Callable<T> callable, String errorMessage) {
		if (callable == null) {
			return null;
		}
		return AccessController.doPrivileged((PrivilegedAction<T>) () -> {
			boolean wasAccessible = member.isAccessible();
			try {
				member.setAccessible(true);
				return callable.call();
			} catch (Exception exception) {
				throw new IllegalStateException(errorMessage, exception);
			} finally {
				member.setAccessible(wasAccessible);
			}
		});
	}

	/**
	 * This method can be used to set (private/public) fields to a given value by reflection. Handling of accessibility
	 * and errors is encapsulated.
	 *
	 * @param field
	 *            the field that's value should be set.
	 * @param target
	 *            the instance of which the field will be set.
	 * @param value
	 *            the new value that the field should be set to.
	 */
	public static void setField(final Field field, Object target, Object value) {
		accessMember(field, () -> field.set(target, value),
				"Cannot set the field [" + field.getName() + "] of instance [" + target + "] to value [" + value + "]");
	}

	/**
	 * Helper method to execute a callback on a given member. This method encapsulates the error handling logic and the
	 * handling of accessibility of the member. The difference to
	 * {@link ReflectionUtils#accessMember(AccessibleObject, Callable, String)} is that this method takes a callback that doesn't
	 * return anything but only creates a sideeffect.
	 *
	 * After the callback is executed the accessibility of the member will be reset to the originally state.
	 *
	 * @param member
	 *            the member that is made accessible to run the callback
	 * @param sideEffect
	 *            the callback that will be executed.
	 * @param errorMessage
	 *            the error message that is used in the exception when something went wrong.
	 *
	 * @throws IllegalStateException
	 *             when something went wrong.
	 */
	public static void accessMember(final AccessibleObject member, final SideEffectWithException sideEffect, String errorMessage) {
		if (sideEffect == null) {
			return;
		}
		AccessController.doPrivileged((PrivilegedAction<?>) () -> {
			boolean wasAccessible = member.isAccessible();
			try {
				member.setAccessible(true);
				sideEffect.call();
			} catch (Exception exception) {
				throw new IllegalStateException(errorMessage, exception);
			} finally {
				member.setAccessible(wasAccessible);
			}
			return null;
		});
	}
}


/*******************************************************************************
 * Copyright 2013 Alexander Casall, Manuel Mauky
 *
 * 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 de.saxsys.mvvmfx.internal.viewloader;

import de.saxsys.mvvmfx.Context;
import de.saxsys.mvvmfx.Scope;
import de.saxsys.mvvmfx.ViewModel;
import de.saxsys.mvvmfx.ViewTuple;
import de.saxsys.mvvmfx.FxmlPath;
import de.saxsys.mvvmfx.internal.ContextImpl;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.util.BuilderFactory;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Consumer;

/**
 * This viewLoader is used to load views that are implementing
 * {@link de.saxsys.mvvmfx.FxmlView}.
 *
 * @author manuel.mauky
 */
public class FxmlViewLoader {

    private static final Logger LOG = LoggerFactory.getLogger(FxmlViewLoader.class);

    /**
     * Load the viewTuple by it`s ViewType.
     *
     * @param viewType
     *            the type of the view to be loaded.
     * @param resourceBundle
     *            the resourceBundle that is passed to the
     *            {@link javafx.fxml.FXMLLoader}.
     * @param codeBehind
     *            the controller instance that is passed to the
     *            {@link javafx.fxml.FXMLLoader}
     * @param root
     *            the root object that is passed to the
     *            {@link javafx.fxml.FXMLLoader}
     * @param viewModel
     *            the viewModel instance that is used when loading the
     *            viewTuple.
     * @param <ViewType>
     *            the generic type of the view.
     * @param <ViewModelType>
     *            the generic type of the viewModel.
     * @param builderFactories a list of custom builder factories. may be null
	 * @return the loaded ViewTuple.
     */
    public <ViewType extends View<? extends ViewModelType>, ViewModelType extends ViewModel> ViewTuple<ViewType, ViewModelType> loadFxmlViewTuple(
			Class<? extends ViewType> viewType, ResourceBundle resourceBundle, ViewType codeBehind, Object root,
			ViewModelType viewModel, Context context, Collection<Scope> providedScopes,
			List<BuilderFactory> builderFactories) {

        final String pathToFXML = createFxmlPath(viewType);
        return loadFxmlViewTuple(viewType, pathToFXML, resourceBundle, codeBehind, root, viewModel, context, providedScopes, builderFactories);
    }

    /**
     * This method is used to create a String with the path to the FXML file for
     * a given View class.
     *
     * This is done by taking the package of the view class (if any) and replace
     * "." with "/". After that the Name of the class and the file ending
     * ".fxml" is appended.
     *
     * If the View class is annotated with @FxmlPath then the String path supplied
     * in the annotation  value will be used.
     *
     * Example: de.saxsys.myapp.ui.MainView as view class will be transformed to
     * "/de/saxsys/myapp/ui/MainView.fxml"
     *
     * Example 2: MainView (located in the default package) will be transformed
     * to "/MainView.fxml"
     *
     * @param viewType
     *            the view class type.
     * @return the path to the fxml file as string.
     */
    private String createFxmlPath(Class<?> viewType) {
        final StringBuilder pathBuilder = new StringBuilder();

        final FxmlPath pathAnnotation = viewType.getDeclaredAnnotation(FxmlPath.class); //Get annotation from view
        final String fxmlPath = Optional.ofNullable(pathAnnotation)
                .map(FxmlPath::value)
                .map(String::trim)
                .orElse("");

        if (fxmlPath.isEmpty()) {
            pathBuilder.append("/");

            if (viewType.getPackage() != null) {
                pathBuilder.append(viewType.getPackage().getName().replaceAll("\\.", "/"));
                pathBuilder.append("/");
            }

            pathBuilder.append(viewType.getSimpleName());
            pathBuilder.append(".fxml");
        } else {
            pathBuilder.append(fxmlPath);
        }

        return pathBuilder.toString();
    }

    /**
     * Load the viewTuple by the path of the fxml file.
     *
     * @param resource
     *            the string path to the fxml file that is loaded.
     * @param resourceBundle
     *            the resourceBundle that is passed to the
     *            {@link javafx.fxml.FXMLLoader}.
     * @param codeBehind
     *            the controller instance that is passed to the
     *            {@link javafx.fxml.FXMLLoader}
     * @param root
     *            the root object that is passed to the
     *            {@link javafx.fxml.FXMLLoader}
     * @param viewModel
     *            the viewModel instance that is used when loading the
     *            viewTuple.
     * @param <ViewType>
     *            the generic type of the view.
     * @param <ViewModelType>
     *            the generic type of the viewModel.
     * @param builderFactories a list of custom builder factories. may be null
     * @return the loaded ViewTuple.
     */
    public <ViewType extends View<? extends ViewModelType>, ViewModelType extends ViewModel> ViewTuple<ViewType, ViewModelType> loadFxmlViewTuple(
            final String resource, ResourceBundle resourceBundle, final ViewType codeBehind, final Object root,
            ViewModelType viewModel, Context parentContext, Collection<Scope> providedScopes,
            List<BuilderFactory> builderFactories) {
        return loadFxmlViewTuple(
                null,
                resource,
                resourceBundle,
                codeBehind,
                root,
                viewModel,
                parentContext,
                providedScopes,
                builderFactories
                );

    }

    /**
     * Load the viewTuple by the path of the fxml file.
     *
     * @param resourceLoader
     *            class used to load resource. When null {@link FxmlViewLoader} is used
     * @param resource
     *            the string path to the fxml file that is loaded.
     * @param resourceBundle
     *            the resourceBundle that is passed to the
     *            {@link javafx.fxml.FXMLLoader}.
     * @param codeBehind
     *            the controller instance that is passed to the
     *            {@link javafx.fxml.FXMLLoader}
     * @param root
     *            the root object that is passed to the
     *            {@link javafx.fxml.FXMLLoader}
     * @param viewModel
     *            the viewModel instance that is used when loading the
     *            viewTuple.
     * @param <ViewType>
     *            the generic type of the view.
     * @param <ViewModelType>
     *            the generic type of the viewModel.
     * @param builderFactories a list of custom builder factories. may be null
	 * @return the loaded ViewTuple.
     */
    private  <ViewType extends View<? extends ViewModelType>, ViewModelType extends ViewModel> ViewTuple<ViewType, ViewModelType> loadFxmlViewTuple(
			final Class<?> resourceLoader, final String resource, ResourceBundle resourceBundle, final ViewType codeBehind, final Object root,
			ViewModelType viewModel, Context parentContext, Collection<Scope> providedScopes,
			List<BuilderFactory> builderFactories) {
        try {

            // FIXME Woanders hin?
            ContextImpl context = ViewLoaderScopeUtils.prepareContext(parentContext, providedScopes);
            //////////////////////////////////////////////////////////////////////

            // for the SceneLifecycle we need to know when the view is put into the scene
            BooleanProperty viewInSceneProperty = new SimpleBooleanProperty();

            final FXMLLoader loader = createFxmlLoader(resourceLoader, resource, resourceBundle, codeBehind, root, viewModel, context, viewInSceneProperty, builderFactories);

            loader.load();

            final ViewType loadedController = loader.getController();
            final Parent loadedRoot = loader.getRoot();

            viewInSceneProperty.bind(loadedRoot.sceneProperty().isNotNull());

            if (loadedController == null) {
                throw new IOException("Could not load the controller for the View " + resource
                        + " maybe your missed the fx:controller in your fxml?");
            }

            // the actually used ViewModel instance. We need this so we can
            // return it in the ViewTuple
            ViewModelType actualViewModel;

            // FIXME CONTEXT

            // if no existing viewModel was provided...
            if (viewModel == null) {
                // ... we try to find the created ViewModel from the codeBehind.
                // this is only possible when the codeBehind has a field for the
                // VM and the VM was injected
                actualViewModel = ViewLoaderReflectionUtils.getExistingViewModel(loadedController);

                // otherwise we create a new ViewModel. This is needed because
                // the ViewTuple has to contain a VM even if
                // the codeBehind doesn't need one
                if (actualViewModel == null) {
                    actualViewModel = ViewLoaderReflectionUtils.createViewModel(loadedController);

                    // it is possible that no viewModel could be created (f.e.
                    // when no generic VM type was specified)
                    // otherwise we need to initialize the created ViewModel
                    // instance.
                    if (actualViewModel != null) {
                        ViewLoaderReflectionUtils.initializeViewModel(actualViewModel);
                    }
                }
            } else {
                actualViewModel = viewModel;
            }
            if (actualViewModel != null) {
                // TODO: Create Testcase for this corner case:
                // If this view is the root view (the one that is loaded with
                // the FluentViewLoader)
                // but in the view there is no injection of the ViewModel
                // only in this case the scope has to be injected here,
                // If the viewModel was already injected in the View, it has
                // it's scopes already injected
                // ViewLoaderReflectionUtils.createAndInjectScopes(actualViewModel,
                // context);
            }

            return new ViewTuple<>(loadedController, loadedRoot, actualViewModel);

        } catch (final IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private FXMLLoader createFxmlLoader(Class<?> resourceLoader, String resource, ResourceBundle resourceBundle, View codeBehind, Object root,
			ViewModel viewModel, ContextImpl context, ObservableBooleanValue viewInSceneProperty,
			List<BuilderFactory> builderFactories) throws IOException {
        // Load FXML file

        Class<?> resourceLoaderClass = resourceLoader == null ? FxmlViewLoader.class : resourceLoader;
        final URL location = resourceLoaderClass.getResource(resource);
        if (location == null) {
            throw new IOException("Error loading FXML - can't load from given resourcepath: " + resource);
        }

        final FXMLLoader fxmlLoader = new FXMLLoader();

        fxmlLoader.setRoot(root);
        fxmlLoader.setResources(resourceBundle);
        fxmlLoader.setLocation(location);

        if(builderFactories == null || builderFactories.isEmpty()) {
        	fxmlLoader.setBuilderFactory(GlobalBuilderFactory.getInstance());
		} else {
			BuilderFactory factory = GlobalBuilderFactory.getInstance().mergeWith(builderFactories);
			fxmlLoader.setBuilderFactory(factory);
		}

        // when the user provides a viewModel but no codeBehind, we need to use
        // the custom controller factory.
        // in all other cases the default factory can be used.
        if (viewModel != null && codeBehind == null) {
            fxmlLoader
                    .setControllerFactory(new ControllerFactoryForCustomViewModel(viewModel, resourceBundle, context, viewInSceneProperty));
        } else {
            fxmlLoader.setControllerFactory(new DefaultControllerFactory(resourceBundle, context, viewInSceneProperty));
        }

        // When the user provides a codeBehind instance we take care of the
        // injection of the viewModel to this
        // controller here.
        if (codeBehind != null) {
            fxmlLoader.setController(codeBehind);

            if (viewModel == null) {
                handleInjection(codeBehind, resourceBundle, context, viewInSceneProperty);
            } else {
                handleInjection(codeBehind, resourceBundle, viewModel, context, viewInSceneProperty);
            }
        }

        return fxmlLoader;
    }

    /**
     * This controller factory will try to create and inject a viewModel
     * instance to every requested controller that is a view.
     */
    private static class DefaultControllerFactory implements Callback<Class<?>, Object> {
        private final ResourceBundle resourceBundle;
        private final ContextImpl context;
        private ObservableBooleanValue viewInSceneProperty;

        public DefaultControllerFactory(ResourceBundle resourceBundle, ContextImpl context, ObservableBooleanValue viewInSceneProperty) {
            this.resourceBundle = resourceBundle;
            this.context = context;
            this.viewInSceneProperty = viewInSceneProperty;
        }

        @Override
        public Object call(Class<?> type) {
            Object controller = DependencyInjector.getInstance().getInstanceOf(type);

            //throw an exception if the fx:controller was of type ViewModel
            if (controller instanceof ViewModel) {
                throw new IllegalStateException("A ViewModel class [" + controller.getClass().getCanonicalName() + "] was referenced in an FXML file"
                        + " as the fx:controller."
                        + " Instead a class that implements FxmlView has to be defined as the fx:controller in the FXML file.");
            }

            if (controller instanceof View) {
                View codeBehind = (View) controller;

                handleInjection(codeBehind, resourceBundle, context, viewInSceneProperty);
            }

            return controller;
        }
    }

    private static void handleInjection(View codeBehind, ResourceBundle resourceBundle, ContextImpl context, ObservableBooleanValue viewInSceneProperty) {
        ViewLoaderReflectionUtils.checkScopesInView(codeBehind);

        ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);

        Consumer<ViewModel> newVmConsumer = viewModel -> {
            ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle);
            ViewLoaderReflectionUtils.createAndInjectScopes(viewModel, context);
            ViewLoaderReflectionUtils.initializeViewModel(viewModel);

            ViewLoaderReflectionUtils.addSceneLifecycleHooks(viewModel, viewInSceneProperty);
        };

        ViewLoaderReflectionUtils.createAndInjectViewModel(codeBehind, newVmConsumer);
        ViewLoaderReflectionUtils.injectContext(codeBehind, context);
    }

    private static void handleInjection(View codeBehind, ResourceBundle resourceBundle, ViewModel viewModel,
            ContextImpl context, ObservableBooleanValue viewInSceneProperty) {
        ViewLoaderReflectionUtils.checkScopesInView(codeBehind);

        ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);

        if (viewModel != null) {
            ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle);
            ViewLoaderReflectionUtils.createAndInjectScopes(viewModel, context);
            ViewLoaderReflectionUtils.injectViewModel(codeBehind, viewModel);
            ViewLoaderReflectionUtils.injectContext(codeBehind, context);

            ViewLoaderReflectionUtils.addSceneLifecycleHooks(viewModel, viewInSceneProperty);
        }
    }

    /**
     * A controller factory that is used for the special case where the user
     * provides an existing viewModel to be used while loading.
     *
     * This factory will use this existing viewModel instance for injection of
     * the first view that is requested from this factory. For
     * all later requests this factory will work the same way as the default
     * factory
     * {@link de.saxsys.mvvmfx.internal.viewloader.FxmlViewLoader.DefaultControllerFactory}
     * .
     *
     * The problem we are facing here is the following: The user wants to load a
     * specific View with a specific ViewModel instance. But this root View
     * (fxml file) can declare other sub views. Only the root View has to get
     * the existing ViewModel instance, all other sub Views have to get their
     * ViewModels via the default way (i.e. DependencyInjection or a new
     * instance every time).
     *
     * But, from the perspective of the controller factory, when a View instance
     * is requested, we can't know if this is the root View or a sub View. How
     * do we know when to use the existing ViewModel instance?
     *
     * To fix this we depend on the standard JavaFX behaviour of the
     * {@link FXMLLoader}: The first instance that the FXMLLoader will request
     * from the controller factory will always be the controller for the root
     * fxml file. In this case we can use the existing ViewModel. All subsequent
     * requests will be handled with the default behaviour.
     */
    private static class ControllerFactoryForCustomViewModel implements Callback<Class<?>, Object> {

        private boolean customViewModelInjected = false;

        private final ViewModel customViewModel;

        private final ResourceBundle resourceBundle;

        private final ContextImpl context;
        private ObservableBooleanValue viewInSceneProperty;

        public ControllerFactoryForCustomViewModel(ViewModel customViewModel, ResourceBundle resourceBundle,
                ContextImpl context, ObservableBooleanValue viewInSceneProperty) {
            this.customViewModel = customViewModel;
            this.resourceBundle = resourceBundle;
            this.context = context;
            this.viewInSceneProperty = viewInSceneProperty;
        }

        @Override
        public Object call(Class<?> type) {
            Object controller = DependencyInjector.getInstance().getInstanceOf(type);

            if (controller instanceof View) {
                View codeBehind = (View) controller;

                if (!customViewModelInjected) {
                    ResourceBundleInjector.injectResourceBundle(customViewModel, resourceBundle);
                    ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);

                    ViewLoaderReflectionUtils.createAndInjectScopes(customViewModel, context);

                    ViewLoaderReflectionUtils.injectViewModel(codeBehind, customViewModel);
                    ViewLoaderReflectionUtils.injectContext(codeBehind, context);

                    ViewLoaderReflectionUtils.addSceneLifecycleHooks(customViewModel, viewInSceneProperty);

                    customViewModelInjected = true;
                    return codeBehind;
                }

                handleInjection(codeBehind, resourceBundle, context, viewInSceneProperty);
            }

            //throw an exception if the fx:controller was of type ViewModel
            if (controller instanceof ViewModel) {
                throw new IllegalStateException("A ViewModel class [" + controller.getClass().getCanonicalName() + "] was referenced in an FXML file"
                        + " as the fx:controller."
                        + " Instead a class that implements FxmlView has to be defined as the fx:controller in the FXML file.");
            }

            return controller;
        }
    }
}


package mil.nga.geopackage.extension.style;

import mil.nga.geopackage.attributes.AttributesCursor;
import mil.nga.geopackage.attributes.AttributesDao;
import mil.nga.geopackage.attributes.AttributesRow;

/**
 * Style DAO for reading style tables
 *
 * @author osbornb
 * @since 3.2.0
 */
public class StyleDao extends AttributesDao {

    /**
     * Constructor
     *
     * @param dao user custom data access object
     */
    public StyleDao(AttributesDao dao) {
        super(dao.getDatabase(), dao.getDb(), dao.getAttributesDb(),
                new StyleTable(dao.getTable()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StyleTable getTable() {
        return (StyleTable) super.getTable();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StyleRow newRow() {
        return new StyleRow(getTable());
    }

    /**
     * Get the style row from the current result set location
     *
     * @param cursor cursor
     * @return style row
     */
    public StyleRow getRow(AttributesCursor cursor) {
        return getRow(cursor.getRow());
    }

    /**
     * Get a style row from the attributes row
     *
     * @param row attributes row
     * @return style row
     */
    public StyleRow getRow(AttributesRow row) {
        return new StyleRow(row);
    }

    /**
     * Query for the style row from a style mapping row
     *
     * @param styleMappingRow style mapping row
     * @return style row
     */
    public StyleRow queryForRow(StyleMappingRow styleMappingRow) {
        StyleRow styleRow = null;

        AttributesRow attributesRow = queryForIdRow(styleMappingRow
                .getRelatedId());
        if (attributesRow != null) {
            styleRow = getRow(attributesRow);
        }

        return styleRow;
    }

}


package mil.nga.geopackage.features.user;

import android.content.ContentValues;

import java.io.IOException;
import java.util.Arrays;

import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.geom.GeoPackageGeometryData;
import mil.nga.geopackage.user.UserRow;
import mil.nga.sf.Geometry;
import mil.nga.sf.GeometryEnvelope;
import mil.nga.sf.GeometryType;

/**
 * Feature Row containing the values from a single cursor row
 *
 * @author osbornb
 */
public class FeatureRow extends UserRow<FeatureColumn, FeatureTable> {

    /**
     * Constructor
     *
     * @param table
     * @param columnTypes
     * @param values
     */
    FeatureRow(FeatureTable table, int[] columnTypes, Object[] values) {
        super(table, columnTypes, values);
    }

    /**
     * Constructor to create an empty row
     *
     * @param table
     */
    FeatureRow(FeatureTable table) {
        super(table);
    }

    /**
     * Copy Constructor
     *
     * @param featureRow feature row to copy
     * @since 1.4.0
     */
    public FeatureRow(FeatureRow featureRow) {
        super(featureRow);
    }

    /**
     * Get the geometry column index
     *
     * @return geometry column index
     */
    public int getGeometryColumnIndex() {
        return getTable().getGeometryColumnIndex();
    }

    /**
     * Get the geometry feature column
     *
     * @return geometry column
     */
    public FeatureColumn getGeometryColumn() {
        return getTable().getGeometryColumn();
    }

    /**
     * {@inheritDoc}
     * Handles geometry columns
     */
    @Override
    public void setValue(int index, Object value) {
        if (index == getGeometryColumnIndex() && value instanceof byte[]) {
            byte[] bytes = (byte[]) value;
            value = new GeoPackageGeometryData(bytes);
        }
        super.setValue(index, value);
    }

    /**
     * Get the geometry
     *
     * @return geometry data
     */
    public GeoPackageGeometryData getGeometry() {
        GeoPackageGeometryData geometryData = null;
        Object value = getValue(getGeometryColumnIndex());
        if (value != null) {
            geometryData = (GeoPackageGeometryData) value;
        }
        return geometryData;
    }

    /**
     * Set the geometry data
     *
     * @param geometryData geometry data
     */
    public void setGeometry(GeoPackageGeometryData geometryData) {
        setValue(getGeometryColumnIndex(), geometryData);
    }

    /**
     * Get the simple features geometry value
     *
     * @return geometry
     * @since 3.1.0
     */
    public Geometry getGeometryValue() {
        GeoPackageGeometryData data = getGeometry();
        Geometry geometry = null;
        if (data != null) {
            geometry = data.getGeometry();
        }
        return geometry;
    }

    /**
     * Get the simple features geometry type
     *
     * @return geometry type
     * @since 3.2.0
     */
    public GeometryType getGeometryType() {
        Geometry geometry = getGeometryValue();
        GeometryType geometryType = null;
        if (geometry != null) {
            geometryType = geometry.getGeometryType();
        }
        return geometryType;
    }

    /**
     * Get the geometry envelope
     *
     * @return geometry envelope
     * @since 3.1.0
     */
    public GeometryEnvelope getGeometryEnvelope() {
        GeoPackageGeometryData data = getGeometry();
        GeometryEnvelope envelope = null;
        if (data != null) {
            envelope = data.getOrBuildEnvelope();
        }
        return envelope;
    }

    /**
     * Copy the row
     *
     * @return row copy
     * @since 3.0.1
     */
    public FeatureRow copy() {
        return new FeatureRow(this);
    }

    /**
     * {@inheritDoc}
     * Handles geometry columns
     */
    @Override
    protected Object copyValue(FeatureColumn column, Object value) {

        Object copyValue = null;

        if (column.isGeometry() && value instanceof GeoPackageGeometryData) {

            GeoPackageGeometryData geometryData = (GeoPackageGeometryData) value;
            try {
                byte[] bytes = geometryData.toBytes();
                byte[] copyBytes = Arrays.copyOf(bytes, bytes.length);
                copyValue = new GeoPackageGeometryData(copyBytes);
            } catch (IOException e) {
                throw new GeoPackageException(
                        "Failed to copy Geometry Data bytes. column: "
                                + column.getName(), e);
            }

        } else {
            copyValue = super.copyValue(column, value);
        }

        return copyValue;
    }

    /**
     * {@inheritDoc}
     * Handles geometry columns
     */
    @Override
    protected void columnToContentValue(ContentValues contentValues,
                                        FeatureColumn column, Object value) {

        if (column.isGeometry()) {

            String columnName = column.getName();

            if (value instanceof GeoPackageGeometryData) {
                GeoPackageGeometryData geometryData = (GeoPackageGeometryData) value;
                try {
                    contentValues.put(columnName, geometryData.toBytes());
                } catch (IOException e) {
                    throw new GeoPackageException(
                            "Failed to write Geometry Data bytes. column: "
                                    + columnName, e);
                }
            } else if (value instanceof byte[]) {
                contentValues.put(columnName, (byte[]) value);
            } else {
                throw new GeoPackageException(
                        "Unsupported update geometry column value type. column: "
                                + columnName + ", value type: "
                                + value.getClass().getName());
            }
        } else {
            super.columnToContentValue(contentValues, column, value);
        }

    }

}


package mil.nga.geopackage.extension.coverage;

import java.io.ByteArrayInputStream;

import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngReader;
import ar.com.hjg.pngj.PngReaderInt;
import ar.com.hjg.pngj.PngWriter;
import mil.nga.geopackage.BoundingBox;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.tiles.user.TileDao;
import mil.nga.geopackage.tiles.user.TileRow;
import mil.nga.sf.proj.Projection;

/**
 * Tiled Gridded Coverage Data, PNG Encoding, Extension
 *
 * @author osbornb
 * @since 2.0.1
 */
public class CoverageDataPng extends CoverageData<CoverageDataPngImage> {

    /**
     * Constructor
     *
     * @param geoPackage        GeoPackage
     * @param tileDao           tile dao
     * @param width             coverage data response width
     * @param height            coverage data response height
     * @param requestProjection request projection
     */
    public CoverageDataPng(GeoPackage geoPackage, TileDao tileDao,
                           Integer width, Integer height, Projection requestProjection) {
        super(geoPackage, tileDao, width,
                height, requestProjection);
    }

    /**
     * Constructor, use the coverage data tables pixel tile size as the request size
     * width and height
     *
     * @param geoPackage GeoPackage
     * @param tileDao    tile dao
     */
    public CoverageDataPng(GeoPackage geoPackage, TileDao tileDao) {
        this(geoPackage, tileDao, null, null, tileDao.getProjection());
    }

    /**
     * Constructor, use the coverage data tables pixel tile size as the request size
     * width and height, request as the specified projection
     *
     * @param geoPackage        GeoPackage
     * @param tileDao           tile dao
     * @param requestProjection request projection
     */
    public CoverageDataPng(GeoPackage geoPackage, TileDao tileDao,
                           Projection requestProjection) {
        this(geoPackage, tileDao, null, null, requestProjection);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CoverageDataPngImage createImage(TileRow tileRow) {
        return new CoverageDataPngImage(tileRow);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double getValue(GriddedTile griddedTile, TileRow tileRow,
                           int x, int y) {
        byte[] imageBytes = tileRow.getTileData();
        double value = getValue(griddedTile, imageBytes, x, y);
        return value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Double getValue(GriddedTile griddedTile,
                           CoverageDataPngImage image, int x, int y) {
        Double value = null;
        if (image.getReader() != null) {
            int pixelValue = image.getPixel(x, y);
            value = getValue(griddedTile, pixelValue);
        } else {
            value = getValue(griddedTile, image.getImageBytes(), x, y);
        }
        return value;
    }

    /**
     * Get the pixel value as a 16 bit unsigned integer value
     *
     * @param imageBytes image bytes
     * @param x          x coordinate
     * @param y          y coordinate
     * @return pixel value
     */
    public int getPixelValue(byte[] imageBytes, int x, int y) {

        PngReaderInt reader = new PngReaderInt(new ByteArrayInputStream(imageBytes));
        validateImageType(reader);
        ImageLineInt row = (ImageLineInt) reader.readRow(y);
        int pixelValue = row.getScanline()[x];
        reader.close();

        return pixelValue;
    }

    /**
     * Get the pixel values of the image as 16 bit unsigned integer values
     *
     * @param imageBytes image bytes
     * @return 16 bit unsigned integer pixel values
     */
    public int[] getPixelValues(byte[] imageBytes) {

        PngReaderInt reader = new PngReaderInt(new ByteArrayInputStream(imageBytes));
        validateImageType(reader);
        int[] pixels = new int[reader.imgInfo.cols * reader.imgInfo.rows];
        int rowNumber = 0;
        while (reader.hasMoreRows()) {
            ImageLineInt row = reader.readRowInt();
            int[] rowValues = row.getScanline();
            System.arraycopy(rowValues, 0, pixels, rowNumber * reader.imgInfo.cols, rowValues.length);
            rowNumber++;
        }
        reader.close();

        return pixels;
    }

    /**
     * Validate that the image type is single channel 16 bit
     *
     * @param reader png reader
     */
    public static void validateImageType(PngReader reader) {
        if (reader == null) {
            throw new GeoPackageException("The image is null");
        }
        if (reader.imgInfo.channels != 1 || reader.imgInfo.bitDepth != 16) {
            throw new GeoPackageException(
                    "The coverage data tile is expected to be a single channel 16 bit unsigned short, channels: "
                            + reader.imgInfo.channels + ", bits: " + reader.imgInfo.bitDepth);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Double getValue(GriddedTile griddedTile,
                           byte[] imageBytes, int x, int y) {
        int pixelValue = getPixelValue(imageBytes, x, y);
        Double value = getValue(griddedTile, pixelValue);
        return value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Double[] getValues(GriddedTile griddedTile,
                              byte[] imageBytes) {
        int[] pixelValues = getPixelValues(imageBytes);
        Double[] values = getValues(griddedTile, pixelValues);
        return values;
    }

    /**
     * Draw a coverage data image tile from the flat array of "unsigned short"
     * pixel values of length tileWidth * tileHeight where each pixel is at: (y
     * * tileWidth) + x
     *
     * @param pixelValues "unsigned short" pixel values of length tileWidth * tileHeight
     * @param tileWidth   tile width
     * @param tileHeight  tile height
     * @return coverage data image tile
     */
    public CoverageDataPngImage drawTile(short[] pixelValues, int tileWidth,
                                         int tileHeight) {

        CoverageDataPngImage image = createImage(tileWidth, tileHeight);
        PngWriter writer = image.getWriter();
        for (int y = 0; y < tileHeight; y++) {
            ImageLineInt row = new ImageLineInt(writer.imgInfo, new int[tileWidth]);
            int[] rowLine = row.getScanline();
            for (int x = 0; x < tileWidth; x++) {
                short pixelValue = pixelValues[(y * tileWidth) + x];
                setPixelValue(rowLine, x, pixelValue);
            }
            writer.writeRow(row);
        }
        writer.end();
        image.flushStream();

        return image;
    }

    /**
     * Draw a coverage data image tile and format as PNG bytes from the flat array
     * of "unsigned short" pixel values of length tileWidth * tileHeight where
     * each pixel is at: (y * tileWidth) + x
     *
     * @param pixelValues "unsigned short" pixel values of length tileWidth * tileHeight
     * @param tileWidth   tile width
     * @param tileHeight  tile height
     * @return coverage data image tile bytes
     */
    public byte[] drawTileData(short[] pixelValues, int tileWidth,
                               int tileHeight) {
        CoverageDataPngImage image = drawTile(pixelValues, tileWidth, tileHeight);
        byte[] bytes = image.getImageBytes();
        return bytes;
    }

    /**
     * Draw a coverage data tile from the double array of "unsigned short" pixel
     * values formatted as short[row][width]
     *
     * @param pixelValues "unsigned short" pixel values as [row][width]
     * @return coverage data image tile
     */
    public CoverageDataPngImage drawTile(short[][] pixelValues) {

        int tileWidth = pixelValues[0].length;
        int tileHeight = pixelValues.length;

        CoverageDataPngImage image = createImage(tileWidth, tileHeight);
        PngWriter writer = image.getWriter();
        for (int y = 0; y < tileHeight; y++) {
            ImageLineInt row = new ImageLineInt(writer.imgInfo, new int[tileWidth]);
            int[] rowLine = row.getScanline();
            for (int x = 0; x < tileWidth; x++) {
                short pixelValue = pixelValues[y][x];
                setPixelValue(rowLine, x, pixelValue);
            }
            writer.writeRow(row);
        }
        writer.end();
        image.flushStream();

        return image;
    }

    /**
     * Draw a coverage data tile and format as PNG bytes from the double array of
     * "unsigned short" pixel values formatted as short[row][width]
     *
     * @param pixelValues "unsigned short" pixel values as [row][width]
     * @return coverage data image tile bytes
     */
    public byte[] drawTileData(short[][] pixelValues) {
        CoverageDataPngImage image = drawTile(pixelValues);
        byte[] bytes = image.getImageBytes();
        return bytes;
    }

    /**
     * Draw a coverage data image tile from the flat array of unsigned 16 bit
     * integer pixel values of length tileWidth * tileHeight where each pixel is
     * at: (y * tileWidth) + x
     *
     * @param unsignedPixelValues unsigned 16 bit integer pixel values of length tileWidth *
     *                            tileHeight
     * @param tileWidth           tile width
     * @param tileHeight          tile height
     * @return coverage data image tile
     */
    public CoverageDataPngImage drawTile(int[] unsignedPixelValues, int tileWidth,
                                         int tileHeight) {

        CoverageDataPngImage image = createImage(tileWidth, tileHeight);
        PngWriter writer = image.getWriter();
        for (int y = 0; y < tileHeight; y++) {
            ImageLineInt row = new ImageLineInt(writer.imgInfo, new int[tileWidth]);
            int[] rowLine = row.getScanline();
            for (int x = 0; x < tileWidth; x++) {
                int unsignedPixelValue = unsignedPixelValues[(y * tileWidth)
                        + x];
                setPixelValue(rowLine, x, unsignedPixelValue);
            }
            writer.writeRow(row);
        }
        writer.end();
        image.flushStream();

        return image;
    }

    /**
     * Draw a coverage data image tile and format as PNG bytes from the flat array
     * of unsigned 16 bit integer pixel values of length tileWidth * tileHeight
     * where each pixel is at: (y * tileWidth) + x
     *
     * @param unsignedPixelValues unsigned 16 bit integer pixel values of length tileWidth *
     *                            tileHeight
     * @param tileWidth           tile width
     * @param tileHeight          tile height
     * @return coverage data image tile bytes
     */
    public byte[] drawTileData(int[] unsignedPixelValues, int tileWidth,
                               int tileHeight) {
        CoverageDataPngImage image = drawTile(unsignedPixelValues, tileWidth,
                tileHeight);
        byte[] bytes = image.getImageBytes();
        return bytes;
    }

    /**
     * Draw a coverage data image tile from the double array of unsigned 16 bit
     * integer pixel values formatted as int[row][width]
     *
     * @param unsignedPixelValues unsigned 16 bit integer pixel values as [row][width]
     * @return coverage data image tile
     */
    public CoverageDataPngImage drawTile(int[][] unsignedPixelValues) {

        int tileWidth = unsignedPixelValues[0].length;
        int tileHeight = unsignedPixelValues.length;

        CoverageDataPngImage image = createImage(tileWidth, tileHeight);
        PngWriter writer = image.getWriter();
        for (int y = 0; y < tileHeight; y++) {
            ImageLineInt row = new ImageLineInt(writer.imgInfo, new int[tileWidth]);
            int[] rowLine = row.getScanline();
            for (int x = 0; x < tileWidth; x++) {
                int unsignedPixelValue = unsignedPixelValues[y][x];
                setPixelValue(rowLine, x, unsignedPixelValue);
            }
            writer.writeRow(row);
        }
        writer.end();
        image.flushStream();

        return image;
    }

    /**
     * Draw a coverage data image tile and format as PNG bytes from the double
     * array of unsigned 16 bit integer pixel values formatted as
     * int[row][width]
     *
     * @param unsignedPixelValues unsigned 16 bit integer pixel values as [row][width]
     * @return coverage data image tile bytes
     */
    public byte[] drawTileData(int[][] unsignedPixelValues) {
        CoverageDataPngImage image = drawTile(unsignedPixelValues);
        byte[] bytes = image.getImageBytes();
        return bytes;
    }

    /**
     * Draw a coverage data image tile from the flat array of coverage data values of length
     * tileWidth * tileHeight where each coverage data value is at: (y * tileWidth) + x
     *
     * @param griddedTile gridded tile
     * @param values      coverage data values of length tileWidth * tileHeight
     * @param tileWidth   tile width
     * @param tileHeight  tile height
     * @return coverage data image tile
     */
    public CoverageDataPngImage drawTile(GriddedTile griddedTile, Double[] values,
                                         int tileWidth, int tileHeight) {

        CoverageDataPngImage image = createImage(tileWidth, tileHeight);
        PngWriter writer = image.getWriter();
        for (int y = 0; y < tileHeight; y++) {
            ImageLineInt row = new ImageLineInt(writer.imgInfo, new int[tileWidth]);
            int[] rowLine = row.getScanline();
            for (int x = 0; x < tileWidth; x++) {
                Double value = values[(y * tileWidth) + x];
                short pixelValue = getPixelValue(griddedTile, value);
                setPixelValue(rowLine, x, pixelValue);
            }
            writer.writeRow(row);
        }
        writer.end();
        image.flushStream();

        return image;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public byte[] drawTileData(GriddedTile griddedTile, Double[] values,
                               int tileWidth, int tileHeight) {
        CoverageDataPngImage image = drawTile(griddedTile, values, tileWidth,
                tileHeight);
        byte[] bytes = image.getImageBytes();
        return bytes;
    }

    /**
     * Draw a coverage data image tile from the double array of unsigned coverage data values
     * formatted as Double[row][width]
     *
     * @param griddedTile gridded tile
     * @param values      coverage data values as [row][width]
     * @return coverage data image tile
     */
    public CoverageDataPngImage drawTile(GriddedTile griddedTile, Double[][] values) {

        int tileWidth = values[0].length;
        int tileHeight = values.length;

        CoverageDataPngImage image = createImage(tileWidth, tileHeight);
        PngWriter writer = image.getWriter();
        for (int y = 0; y < tileHeight; y++) {
            ImageLineInt row = new ImageLineInt(writer.imgInfo, new int[tileWidth]);
            int[] rowLine = row.getScanline();
            for (int x = 0; x < tileWidth; x++) {
                Double value = values[y][x];
                short pixelValue = getPixelValue(griddedTile, value);
                setPixelValue(rowLine, x, pixelValue);
            }
            writer.writeRow(row);
        }
        writer.end();
        image.flushStream();

        return image;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public byte[] drawTileData(GriddedTile griddedTile, Double[][] values) {
        CoverageDataPngImage image = drawTile(griddedTile, values);
        byte[] bytes = image.getImageBytes();
        return bytes;
    }

    /**
     * Create a new 16 bit single channel image
     *
     * @param tileWidth  tile width
     * @param tileHeight tile height
     * @return image
     */
    public CoverageDataPngImage createImage(int tileWidth, int tileHeight) {
        ImageInfo imageInfo = new ImageInfo(tileWidth, tileHeight, 16, false, true, false);
        CoverageDataPngImage image = new CoverageDataPngImage(imageInfo);
        return image;
    }

    /**
     * Set the pixel value
     *
     * @param row        image line row
     * @param x          x coordinate
     * @param pixelValue pixel value
     */
    public void setPixelValue(ImageLineInt row, int x,
                              short pixelValue) {
        setPixelValue(row.getScanline(), x, pixelValue);
    }

    /**
     * Set the pixel value
     *
     * @param row        row array
     * @param x          x coordinate
     * @param pixelValue pixel value
     */
    public void setPixelValue(int[] row, int x,
                              short pixelValue) {
        row[x] = pixelValue;
    }

    /**
     * Set the pixel value
     *
     * @param row                image line row
     * @param x                  x coordinate
     * @param unsignedPixelValue unsigned pixel value
     */
    public void setPixelValue(ImageLineInt row, int x,
                              int unsignedPixelValue) {
        short pixelValue = getPixelValue(unsignedPixelValue);
        setPixelValue(row, x, pixelValue);
    }

    /**
     * Set the pixel value
     *
     * @param row                row array
     * @param x                  x coordinate
     * @param unsignedPixelValue unsigned pixel value
     */
    public void setPixelValue(int[] row, int x,
                              int unsignedPixelValue) {
        short pixelValue = getPixelValue(unsignedPixelValue);
        setPixelValue(row, x, pixelValue);
    }

    /**
     * Create the coverage data tile table with metadata and extension
     *
     * @param geoPackage               GeoPackage
     * @param tableName                table name
     * @param contentsBoundingBox      contents bounding box
     * @param contentsSrsId            contents srs id
     * @param tileMatrixSetBoundingBox tile matrix set bounding box
     * @param tileMatrixSetSrsId       tile matrix set srs id
     * @return coverage data
     */
    public static CoverageDataPng createTileTableWithMetadata(
            GeoPackage geoPackage, String tableName,
            BoundingBox contentsBoundingBox, long contentsSrsId,
            BoundingBox tileMatrixSetBoundingBox, long tileMatrixSetSrsId) {

        CoverageDataPng coverageData = (CoverageDataPng) CoverageData
                .createTileTableWithMetadata(geoPackage, tableName,
                        contentsBoundingBox, contentsSrsId,
                        tileMatrixSetBoundingBox, tileMatrixSetSrsId,
                        GriddedCoverageDataType.INTEGER);
        return coverageData;
    }

}


package mil.nga.geopackage.tiles.user;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory.Options;

import java.io.IOException;

import mil.nga.geopackage.io.BitmapConverter;
import mil.nga.geopackage.user.UserRow;

/**
 * Tile Row containing the values from a single cursor row
 *
 * @author osbornb
 */
public class TileRow extends UserRow<TileColumn, TileTable> {

    /**
     * Constructor
     *
     * @param table
     * @param columnTypes
     * @param values
     */
    TileRow(TileTable table, int[] columnTypes, Object[] values) {
        super(table, columnTypes, values);
    }

    /**
     * Constructor to create an empty row
     *
     * @param table
     */
    TileRow(TileTable table) {
        super(table);
    }

    /**
     * Copy Constructor
     *
     * @param tileRow tile row to copy
     * @since 1.4.0
     */
    public TileRow(TileRow tileRow) {
        super(tileRow);
    }

    /**
     * Get the zoom level column index
     *
     * @return zoom level column index
     */
    public int getZoomLevelColumnIndex() {
        return getTable().getZoomLevelColumnIndex();
    }

    /**
     * Get the zoom level column
     *
     * @return zoom level column
     */
    public TileColumn getZoomLevelColumn() {
        return getTable().getZoomLevelColumn();
    }

    /**
     * Get the zoom level
     *
     * @return zoom level
     */
    public long getZoomLevel() {
        return ((Number) getValue(getZoomLevelColumnIndex())).longValue();
    }

    /**
     * Set the zoom level
     *
     * @param zoomLevel zoom level
     */
    public void setZoomLevel(long zoomLevel) {
        setValue(getZoomLevelColumnIndex(), zoomLevel);
    }

    /**
     * Get the tile column column index
     *
     * @return tile column column index
     */
    public int getTileColumnColumnIndex() {
        return getTable().getTileColumnColumnIndex();
    }

    /**
     * Get the tile column column
     *
     * @return tile column column
     */
    public TileColumn getTileColumnColumn() {
        return getTable().getTileColumnColumn();
    }

    /**
     * Get the tile column
     *
     * @return tile column
     */
    public long getTileColumn() {
        return ((Number) getValue(getTileColumnColumnIndex())).longValue();
    }

    /**
     * Set the tile column
     *
     * @param tileColumn tile column
     */
    public void setTileColumn(long tileColumn) {
        setValue(getTileColumnColumnIndex(), tileColumn);
    }

    /**
     * Get the tile row column index
     *
     * @return tile row column index
     */
    public int getTileRowColumnIndex() {
        return getTable().getTileRowColumnIndex();
    }

    /**
     * Get the tile row column
     *
     * @return tile column
     */
    public TileColumn getTileRowColumn() {
        return getTable().getTileRowColumn();
    }

    /**
     * Get the tile row
     *
     * @return tile row
     */
    public long getTileRow() {
        return ((Number) getValue(getTileRowColumnIndex())).longValue();
    }

    /**
     * Set the tile row
     *
     * @param tileRow tile row
     */
    public void setTileRow(long tileRow) {
        setValue(getTileRowColumnIndex(), tileRow);
    }

    /**
     * Get the tile data column index
     *
     * @return tile data column index
     */
    public int getTileDataColumnIndex() {
        return getTable().getTileDataColumnIndex();
    }

    /**
     * Get the tile data column
     *
     * @return tile data column
     */
    public TileColumn getTileDataColumn() {
        return getTable().getTileDataColumn();
    }

    /**
     * Get the tile data
     *
     * @return tile data bytes
     */
    public byte[] getTileData() {
        return (byte[]) getValue(getTileDataColumnIndex());
    }

    /**
     * Set the tile data
     *
     * @param tileData tile data bytes
     */
    public void setTileData(byte[] tileData) {
        setValue(getTileDataColumnIndex(), tileData);
    }

    /**
     * Get the tile data bitmap
     *
     * @return tile data bitmap
     */
    public Bitmap getTileDataBitmap() {
        return getTileDataBitmap(null);
    }

    /**
     * Get the tile data bitmap with decoding options
     *
     * @param options bitmap options
     * @return tile data bitmap
     */
    public Bitmap getTileDataBitmap(Options options) {
        return BitmapConverter.toBitmap(getTileData(), options);
    }

    /**
     * Set the tile data from a full quality bitmap
     *
     * @param bitmap tile bitmap
     * @param format compress format
     * @throws IOException upon failure
     */
    public void setTileData(Bitmap bitmap, CompressFormat format)
            throws IOException {
        setTileData(bitmap, format, 100);
    }

    /**
     * Set the tile data from a bitmap
     *
     * @param bitmap  tile bitmap
     * @param format  compress format
     * @param quality quality
     * @throws IOException upon failure
     */
    public void setTileData(Bitmap bitmap, CompressFormat format, int quality)
            throws IOException {
        byte[] tileData = BitmapConverter.toBytes(bitmap, format, quality);
        setTileData(tileData);
    }

    /**
     * Copy the row
     *
     * @return row copy
     * @since 3.0.1
     */
    public TileRow copy() {
        return new TileRow(this);
    }

}


package mil.nga.geopackage.db;

import android.database.Cursor;

import com.j256.ormlite.android.AndroidConnectionSource;
import com.j256.ormlite.support.ConnectionSource;

import java.util.List;

import mil.nga.geopackage.GeoPackageException;

/**
 * GeoPackage Android Connection wrapper
 *
 * @author osbornb
 */
public class GeoPackageConnection extends GeoPackageCoreConnection {

    /**
     * Name column
     */
    private static final String NAME_COLUMN = "name";

    /**
     * Database connection
     */
    private final GeoPackageDatabase db;

    /**
     * Constructor
     *
     * @param db GeoPackage connection
     */
    public GeoPackageConnection(GeoPackageDatabase db) {
        super(new AndroidConnectionSource(db.getDb()));
        this.db = db;
    }

    /**
     * Get the database connection
     *
     * @return GeoPackage database
     */
    public GeoPackageDatabase getDb() {
        return db;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ConnectionSource getConnectionSource() {
        return connectionSource;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void execSQL(String sql) {
        db.execSQL(sql);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int delete(String table, String whereClause, String[] whereArgs) {
        return db.delete(table, whereClause, whereArgs);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int count(String table, String where, String[] args) {

        StringBuilder countQuery = new StringBuilder();
        countQuery.append("select count(*) from ").append(CoreSQLUtils.quoteWrap(table));
        if (where != null) {
            countQuery.append(" where ").append(where);
        }
        String sql = countQuery.toString();

        int count = querySingleInteger(sql, args, true);

        return count;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Integer min(String table, String column, String where, String[] args) {

        Integer min = null;
        if (count(table, where, args) > 0) {
            StringBuilder minQuery = new StringBuilder();
            minQuery.append("select min(").append(CoreSQLUtils.quoteWrap(column)).append(") from ")
                    .append(CoreSQLUtils.quoteWrap(table));
            if (where != null) {
                minQuery.append(" where ").append(where);
            }
            String sql = minQuery.toString();

            min = querySingleInteger(sql, args, false);
        }

        return min;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Integer max(String table, String column, String where, String[] args) {

        Integer max = null;
        if (count(table, where, args) > 0) {
            StringBuilder maxQuery = new StringBuilder();
            maxQuery.append("select max(").append(CoreSQLUtils.quoteWrap(column)).append(") from ")
                    .append(CoreSQLUtils.quoteWrap(table));
            if (where != null) {
                maxQuery.append(" where ").append(where);
            }
            String sql = maxQuery.toString();

            max = querySingleInteger(sql, args, false);
        }

        return max;
    }

    /**
     * Query the SQL for a single integer result
     *
     * @param sql               SQL
     * @param args              arguments
     * @param allowEmptyResults true to accept empty results as a 0 return
     * @return int result
     */
    private int querySingleInteger(String sql, String[] args, boolean allowEmptyResults) {

        int result = 0;

        Object value = querySingleResult(sql, args, 0,
                GeoPackageDataType.MEDIUMINT);
        if (value != null) {
            result = ((Number) value).intValue();
        } else if (!allowEmptyResults) {
            throw new GeoPackageException(
                    "Failed to query for single result. SQL: " + sql);
        }

        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        super.close();
        db.close();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean columnExists(String tableName, String columnName) {

        boolean exists = false;

        Cursor cursor = rawQuery("PRAGMA table_info(" + CoreSQLUtils.quoteWrap(tableName) + ")", null);
        try {
            int nameIndex = cursor.getColumnIndex(NAME_COLUMN);
            while (cursor.moveToNext()) {
                String name = cursor.getString(nameIndex);
                if (columnName.equals(name)) {
                    exists = true;
                    break;
                }
            }
        } finally {
            cursor.close();
        }

        return exists;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object querySingleResult(String sql, String[] args, int column,
                                    GeoPackageDataType dataType) {
        CursorResult result = wrapQuery(sql, args);
        Object value = ResultUtils.buildSingleResult(result, column, dataType);
        return value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Object> querySingleColumnResults(String sql, String[] args,
                                                 int column, GeoPackageDataType dataType, Integer limit) {
        CursorResult result = wrapQuery(sql, args);
        List<Object> results = ResultUtils.buildSingleColumnResults(result,
                column, dataType, limit);
        return results;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<List<Object>> queryResults(String sql, String[] args,
                                           GeoPackageDataType[] dataTypes, Integer limit) {
        CursorResult result = wrapQuery(sql, args);
        List<List<Object>> results = ResultUtils.buildResults(result,
                dataTypes, limit);
        return results;
    }

    /**
     * Perform a raw database query
     *
     * @param sql  sql command
     * @param args arguments
     * @return cursor
     * @since 1.2.1
     */
    public Cursor rawQuery(String sql, String[] args) {
        return db.rawQuery(sql, args);
    }

    /**
     * Perform the query and wrap as a result
     *
     * @param sql           sql statement
     * @param selectionArgs selection arguments
     * @return result
     * @since 3.1.0
     */
    public CursorResult wrapQuery(String sql,
                                  String[] selectionArgs) {
        return new CursorResult(rawQuery(sql, selectionArgs));
    }

}