All Downloads are FREE. Search and download functionalities are using the official Maven repository.

prompto.intrinsic.PromptoRoot Maven / Gradle / Ivy

The newest version!
package prompto.intrinsic;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;

import prompto.compiler.CompilerUtils;
import prompto.compiler.PromptoClassLoader;
import prompto.declaration.CategoryDeclaration;
import prompto.error.NotStorableError;
import prompto.grammar.Identifier;
import prompto.runtime.ApplicationContext;
import prompto.runtime.Context;
import prompto.store.DataStore;
import prompto.store.IStorable;
import prompto.store.IStored;
import prompto.store.IStoredIterable;
import prompto.store.InvalidValueError;

public abstract class PromptoRoot extends PromptoStorableBase implements IMutable, IDocumentProducer, IDocumentValueProducer, IJsonNodeProducer {

	@SuppressWarnings("resource")
	public static PromptoRoot newInstance(IStored stored, boolean mutable) {
		if(stored==null) // happens on an unsuccessful fetchOne
			return null;
		else try {
			Object list = stored.getData("category");
			@SuppressWarnings("unchecked")
			String name = ((PromptoList)list).getLast();
			String concreteName = CompilerUtils.getCategoryConcreteType(name).getTypeName();
			PromptoClassLoader loader = PromptoClassLoader.getInstance();
			if(loader==null)
				throw new UnsupportedOperationException("newPromptoRoot can only be used in compiled mode!");
			Class klass = Class.forName(concreteName, true, loader);
			Constructor cons = klass.getConstructor(IStored.class);
			Object instance = cons.newInstance(stored);
			((PromptoRoot)instance).setMutable(mutable);
			return (PromptoRoot)instance;
		} catch (Exception e) {
			throw new RuntimeException(e); // TODO for now
		}
	}
	
	public static PromptoRoot newInstanceFromDbIdRef(Object value) {
		if(value instanceof PromptoRoot)
			return (PromptoRoot)value;
		value = DataStore.getInstance().convertToDbId(value);
		if(value instanceof PromptoDbId)
			value = DataStore.getInstance().fetchUnique((PromptoDbId)value);
		if(value instanceof IStored)
			return newInstance((IStored)value, false);
		else
			return (PromptoRoot)value; // will eventually throw an InvalidCastException 
	}
	
	public static IterableWithCounts newIterable(IStoredIterable iterable) {
		return new IterableWithCounts() {
			
			@Override
			public Long getCount() {
				return iterable.count();
			}
			
			@Override
			public Long getTotalCount() {
				return iterable.totalCount();
			}

			@Override
			public Iterator iterator() {
				return new Iterator() {
					
					Iterator iterator = iterable.iterator();
					
					@Override public boolean hasNext() { 
						return iterator.hasNext(); 
					}
					@Override public PromptoRoot next() { 
						return newInstance(iterator.next(), false); 
					}
				};
			}
		};
	}
	
	
	public static Object getStorableData(Object value) {
		if(value instanceof PromptoEnum)
			return ((PromptoEnum)value).getName();
		else if(value instanceof PromptoRoot)
			return ((PromptoRoot)value).getStorableData();
		else
			return null;
	}
	
	@SuppressWarnings("unchecked")
	public static  T convertObjectToExact(Object o, Class klass) {
		if(o==null)
			return null;
		else if(klass.isInstance(o))
			return (T)o;
		// TODO: convert Document to instance
		else
			throw new InvalidValueError("Expected a " + klass.getSimpleName() +", got " + o.getClass().getSimpleName());
	}

	protected IStorable storable;
	protected boolean mutable;
	
	protected PromptoRoot() {
	}
	
	protected PromptoRoot(IStored stored) {
		if(stored!=null)
			dbId = stored.getDbId();
	}
	
	public CategoryDeclaration getCategory() {
		String name = getCategoryName();
		Context context = ApplicationContext.get();
		return context.getRegisteredDeclaration(CategoryDeclaration.class, new Identifier(name));
	}
	
	private String getCategoryName() {
		String[] parts = this.getClass().getName().split("%");
		return parts[parts.length-1];
	}

	public IStorable getStorable() {
		return storable;
	}
	
	public final Object getStorableData() {
		// this is called when storing the instance as a field value, so we just return the dbId
		// the instance data itself will be collected as part of collectStorables
		if(this.storable==null)
			throw new NotStorableError();
		else
			return this.getOrCreateDbId();
	}
	
	private PromptoDbId getOrCreateDbId() throws NotStorableError {
		PromptoDbId dbId = getDbId();
		if(dbId==null) {
			dbId = this.storable.getOrCreateDbId();
			setDbId(dbId);
		}
		return dbId;
	}

	/* not a great name, but avoids collision with field setters */
	protected final void setStorable(String name, Object value) {
		if(storable!=null)
			storable.setData(name, value);
	}
	
	@Override
	public boolean isMutable() {
		return mutable;
	}
	
	@Override
	public void setMutable(boolean mutable) {
		this.mutable = mutable;
	}
	
	@Override
	public void checkMutable() {
		if(!this.mutable) 
			PromptoException.throwEnumeratedException("NOT_MUTABLE");
	}
	
	@Override
	public void checkImmutable() {
		if(this.mutable) 
			PromptoException.throwEnumeratedException("NOT_MUTABLE");
	}
	
	@Override
	public PromptoRoot toMutable() {
		try {
			Constructor cons = this.getClass().getConstructor();
			PromptoRoot instance = (PromptoRoot)cons.newInstance();
			instance.mutable = true;
			List fields = collectFields();
			for(Field field: fields) {
				field.setAccessible(true);
				Object value = field.get(this);
				field.set(instance, value);
			}
			return instance;
		} catch(Exception e) {
			throw new RuntimeException(e);
		}

	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append('{');
		// sb.append("{id:");
		// sb.append(System.identityHashCode(this));
		// sb.append(", ");
		List fields = collectFields();
		fields = fields.stream().filter(field -> getFieldValue(this, field) != null).collect(Collectors.toList());
		fields.forEach(field-> {
			sb.append(field.getName());
			sb.append(':');
			sb.append(String.valueOf(getFieldValue(this, field)));
			sb.append(", ");
		});
		if(sb.length()>1)
			sb.setLength(sb.length()-", ".length()); // remove trailing ", "
		sb.append('}');
		return sb.toString();
	}

	protected static Object getFieldValue(Object instance, Field field) {
		boolean canAccess = field.canAccess(instance);
		return canAccess ? getAccessibleFieldValue(instance, field) : getInaccessibleFieldValue(instance, field);
	}
	
	private static Object getInaccessibleFieldValue(Object instance, Field field) {
		try {
			field.setAccessible(true);
			return getAccessibleFieldValue(instance, field);
		} finally {
			field.setAccessible(false);
		}
	}


	private static Object getAccessibleFieldValue(Object instance, Field field) {
		try {
			return field.get(instance);
		} catch (Exception e) {
			return "";
		} 
	}
	
	private List collectFields() {
		List list = new ArrayList<>();
		collectFields(list, this.getClass());
		return list;
	}

	private static Set hiddenFields = new HashSet<>(Arrays.asList("category", "dbId", "storable", "mutable", "hiddenFields"));
	
	private void collectFields(List list, Class klass) {
		if(Object.class==klass)
			return;
		collectFields(list, klass.getSuperclass());
		list.addAll(
				Arrays.asList(klass.getDeclaredFields())
				.stream()
				.filter((f)->
					!hiddenFields.contains(f.getName()))
				.collect(Collectors.toList()));
	}

	public void collectStorables(Consumer collector) {
		if(storable!=null && storable.isDirty()) {
			getOrCreateDbId();
			collector.accept(storable);
		}
	}

	@Override
	public PromptoDocument toDocumentValue() {
		return toDocument();
	}
	
	@Override
	public PromptoDocument toDocument() {
		PromptoDocument doc = new PromptoDocument<>(); 
		List fields = collectFields();
		fields.forEach(field-> {
			Object value = getFieldValue(this, field);
			if(value instanceof IDocumentValueProducer)
				value = ((IDocumentValueProducer)value).toDocumentValue();
			doc.put(field.getName(), value);
		});
		return doc;
	}
	
	@Override
	public int hashCode() {
		var values = collectFields().stream()
				.map(field -> getFieldValue(this, field))
				.collect(Collectors.toList())
				.toArray(new Object[0]);
		return Objects.hash(values);
	}


	@Override
	public boolean equals(Object other) {
		if(other==null || other.getClass()!=this.getClass())
			return false;
		for(Field field : collectFields()) {
			Object thisValue = getFieldValue(this, field);
			Object otherValue = getFieldValue(other, field);
			if(!Objects.equals(thisValue, otherValue))
				return false;
		}
		return true;
	}
	
	@Override
	public JsonNode toJsonNode() {
		ObjectNode object = JsonNodeFactory.instance.objectNode();
		for(Field field : collectFields()) {
			Object value = getFieldValue(this, field);
			object.set(field.getName(), PromptoConverter.toJsonNode(value));
		}
		Object dbId = getDbId();
		object.set("dbId", JsonNodeFactory.instance.textNode(String.valueOf(dbId)));
		return object;
	}

	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy