/*******************************************************************************
 * Copyright (c) 2005-2014, G. Weirich, Elexis and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    G. Weirich - initial implementation
 *    MEDEVIT - <office@medevit.at>
 *******************************************************************************/
package ch.elexis.data;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;

import ch.elexis.core.constants.StringConstants;
import ch.elexis.core.data.activator.CoreHub;
import ch.elexis.core.data.constants.ExtensionPointConstantsData;
import ch.elexis.core.data.status.ElexisStatus;
import ch.elexis.core.data.util.Extensions;
import ch.elexis.core.exceptions.PersistenceException;
import ch.rgw.tools.ExHandler;

public class PersistentObjectFactory implements IExecutableExtension {
	
	private HashMap<String, PersistentObjectFactory> poFactoryCache = new HashMap<>();
	
	private static final String CLASS = "Class";
	
	/**
	 * Create a template of the provided class type, without creating a corresponding database
	 * entry. If the core is not able to create the resp. class, all plug-ins contributing a
	 * PersistentObjectFactory will be queried.
	 * 
	 * @param type
	 *            the requested class type
	 * @return a non-persisted object of class type or <code>null</code>
	 * @throws PersistenceException
	 */
	@SuppressWarnings("unchecked")
	public PersistentObject createTemplate(Class type){
		// try to resolve factory from cache
		PersistentObjectFactory persistentObjectFactory = poFactoryCache.get(type.getName());
		if (persistentObjectFactory != null) {
			return persistentObjectFactory.doCreateTemplate(type);
		}
		
		try {
			PersistentObject po = (PersistentObject) type.newInstance();
			return po;
		} catch (IllegalAccessException ex) {
			List<PersistentObjectFactory> contributedFactories =
				Extensions.getClasses(ExtensionPointConstantsData.PERSISTENT_REFERENCE, CLASS);
			for (PersistentObjectFactory po : contributedFactories) {
				PersistentObject ret = po.doCreateTemplate(type);
				if (ret != null) {
					// found a responsible factory, cache it
					poFactoryCache.put(type.getName(), po);
					return ret;
				}
			}
		} catch (Exception ex) {
			ElexisStatus status =
				new ElexisStatus(ElexisStatus.ERROR, CoreHub.PLUGIN_ID, ElexisStatus.CODE_NONE,
					"create: Couldn't create object " + ex.getMessage(), ex,
					ElexisStatus.LOG_ERRORS);
			throw new PersistenceException(status);
		}
		return null;
	}
	
	protected PersistentObject doCreateTemplate(Class<? extends PersistentObject> typ){
		try {
			return (PersistentObject) typ.newInstance();
		} catch (Exception ex) {
			ExHandler.handle(ex);
			return null;
		}
	}
	
	/**
	 * Helper-Funktion, die Objekte eines beliebigen abgeleiteten Typs mit beliebigen Feldvorgaben
	 * erstellen kann.
	 * 
	 * @param typ
	 *            Die Klasse des zu erstellenden Objekts
	 * @param fields
	 *            Die initial zu belegenden Felder. ID darf nicht angegeben werden.
	 * @param values
	 *            Die Werte für die Felder
	 * @return Das Objekt bei Erfolg, sonst null
	 */
	public PersistentObject create(Class<? extends PersistentObject> typ, String[] fields,
		String[] values){
		PersistentObject template = createTemplate(typ);
		template.create(null);
		if ((template != null) && (template.set(fields, values) == true)) {
			return template;
		}
		return null;
	}
	
	/**
	 * Create an object of a derived class given a pseudo de-serialization code, e.g.
	 * <code>ch.elexis.artikel_ch.data.Medikament::ca8bb5c27bdd67d5f011821</code>. If the object can
	 * not be created by the core, all plug-ins contributing a {@link #PersistentObjectFactory()}
	 * are queried.
	 * 
	 * @param code
	 *            the storeToString as shown in the above example
	 * @return the de-serialized object, or <code>null</code>
	 */
	@SuppressWarnings("unchecked")
	public PersistentObject createFromString(final String code){
		if (code == null) {
			return null;
		}
		
		String[] ci = code.split(StringConstants.DOUBLECOLON);
		if (ci.length != 2)
			return null;
		
		// try to resolve factory from cache
		PersistentObjectFactory persistentObjectFactory = poFactoryCache.get(ci[0]);
		if (persistentObjectFactory != null) {
			return persistentObjectFactory.createFromString(code);
		}
		
		try {
			Class clazz = Class.forName(ci[0]);
			Method load = clazz.getMethod("load", new Class[] {
				String.class
			});
			return (PersistentObject) (load.invoke(null, new Object[] {
				ci[1]
			}));
		} catch (ClassNotFoundException ex) {
			List<PersistentObjectFactory> contributedFactories =
				Extensions.getClasses(ExtensionPointConstantsData.PERSISTENT_REFERENCE, CLASS);
			for (PersistentObjectFactory po : contributedFactories) {
				PersistentObject ret = po.createFromString(code);
				if (ret != null) {
					// found a responsible factory, cache it
					poFactoryCache.put(ci[0], po);
					return ret;
				}
			}
		} catch (Exception ex) {
			ExHandler.handle(ex);
			return null;
		}
		return null;
		
	}
	
	public void setInitializationData(IConfigurationElement config, String propertyName, Object data)
		throws CoreException{
		
	}
	
	/**
	 * Ask the plug-in local classloader to return an instance of a Class as given by the first part
	 * of the storeToString (e.g. ch.elexis.eigenartikel.Eigenartikel::392393253959)
	 * 
	 * @param fullyQualifiedClassName
	 * @return Class if found, else null
	 */
	public Class getClassforName(String fullyQualifiedClassName){
		Class ret = null;
		try {
			ret = Class.forName(fullyQualifiedClassName);
			return ret;
		} catch (ClassNotFoundException e) {
			return ret;
		}
	}
}

@@ -1245,16 +1245,23 @@
 private 
+static 
 HashMap%3C

make PersistentObjectFactory#poFactoryCache static