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

org.emfjson.jackson.databind.deser.EObjectDeserializer Maven / Gradle / Ivy

/*
 * Copyright (c) 2015 Guillaume Hillairet.
 * 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:
 *     Guillaume Hillairet - initial API and implementation
 *
 */
package org.emfjson.jackson.databind.deser;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.*;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.emfjson.EMFJs;
import org.emfjson.common.Cache;
import org.emfjson.common.EObjects;
import org.emfjson.common.ReferenceEntries;
import org.emfjson.jackson.JacksonOptions;
import org.emfjson.jackson.errors.JSONException;
import org.emfjson.jackson.resource.JsonResource;

import java.io.IOException;
import java.util.Map;

public class EObjectDeserializer extends JsonDeserializer implements ContextualDeserializer {

	private final Cache cache = new Cache();
	private final ResourceSet resourceSet;
	private final JacksonOptions options;

	public EObjectDeserializer(ResourceSet resourceSet, JacksonOptions options) {
		this.resourceSet = resourceSet;
		this.options = options;
	}

	@Override
	public boolean isCachable() {
		return true;
	}

	@Override
	public EObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
		prepareContext(ctxt, null);

		return doDeserialize(jp, findRoot(ctxt), ctxt);
	}

	@Override
	public EObject deserialize(JsonParser jp, DeserializationContext ctxt, EObject intoValue) throws IOException {
		if (intoValue == null) {
			return null;
		}

		prepareContext(ctxt, null);

		final Resource resource = (Resource) ctxt.getAttribute("resource");
		final ReferenceEntries entries = (ReferenceEntries) ctxt.getAttribute("entries");

		while (jp.nextToken() != JsonToken.END_OBJECT) {
			final String fieldName = jp.getCurrentName();

			if (options.idField.equalsIgnoreCase(fieldName)) {
				options.idDeserializer.deserialize(jp, intoValue, ctxt);
			} else {
				readFeature(jp, intoValue, fieldName, ctxt, resource, entries);
			}
		}

		return postDeserialize(null, intoValue, null, ctxt);
	}

	public EObject deserialize(JsonParser jp, DeserializationContext ctxt, EReference containment) throws IOException {
		EClass defaultType = null;

		if (containment != null) {
			defaultType = containment.getEReferenceType();

			if (defaultType.isAbstract()) {
				defaultType = null;
			}
		}

		return doDeserialize(jp, defaultType, ctxt);
	}

	protected EObject doDeserialize(JsonParser jp, EClass defaultType, DeserializationContext ctxt) throws IOException {
		EObject current = null;
		final Resource resource = (Resource) ctxt.getAttribute("resource");
		final ReferenceEntries entries = (ReferenceEntries) ctxt.getAttribute("entries");
		final TokenBuffer buffer = new TokenBuffer(jp);

		while (jp.nextToken() != JsonToken.END_OBJECT) {
			final String fieldName = jp.getCurrentName();

			if (options.typeField.equalsIgnoreCase(fieldName)) {

				if (current == null) {
					current = create(jp, ctxt);
				}

			} else if (options.idField.equalsIgnoreCase(fieldName)) {

				options.idDeserializer.deserialize(jp, current, ctxt);

			} else {

				if (current != null) {
					readFeature(jp, current, fieldName, ctxt, resource, entries);
				} else {
					buffer.copyCurrentStructure(jp);
				}

			}
		}

		return postDeserialize(buffer, current, defaultType, ctxt);
	}

	protected EObject postDeserialize(TokenBuffer buffer, EObject object, EClass defaultType, DeserializationContext ctxt) throws IOException {
		if (object == null && defaultType == null) {
			return null;
		}

		if (object == null) {
			object = EcoreUtil.create(defaultType);
		}

		if (buffer != null) {
			final Resource resource = (Resource) ctxt.getAttribute("resource");
			final ReferenceEntries entries = (ReferenceEntries) ctxt.getAttribute("entries");
			final JsonParser bufferedParser = buffer.asParser();

			while (bufferedParser.nextToken() != null) {
				readFeature(bufferedParser, object, bufferedParser.getCurrentName(), ctxt, resource, entries);
			}

			bufferedParser.close();
			buffer.close();
		}

		return object;
	}

	private EClass findRoot(DeserializationContext ctxt) {
		Object root = ctxt.getAttribute(EMFJs.OPTION_ROOT_ELEMENT);
		if (root instanceof EClass) {
			return (EClass) root;
		}

		return options.rootElement;
	}

	private void readFeature(JsonParser jp, EObject current,
							 String fieldName, DeserializationContext ctxt,
							 Resource resource, ReferenceEntries entries) throws IOException {

		final EClass eClass = current.eClass();
		final EStructuralFeature feature = cache.getEStructuralFeature(eClass, fieldName);

		if (feature != null) {

			if (jp.getCurrentToken() == JsonToken.FIELD_NAME) {
				jp.nextToken();
			}

			if (feature instanceof EAttribute) {
				readAttribute(jp, current, (EAttribute) feature, resource, ctxt);
			} else {
				EReference reference = (EReference) feature;

				if (reference.isContainment()) {
					readContainment(jp, current, ctxt, reference, resource, entries);
				} else {
					readReference(jp, current, reference, entries, ctxt);
				}
			}

		} else {
			if (resource != null) {
				resource.getErrors().add(new JSONException(
						"Unknown feature " + fieldName,
						jp.getCurrentLocation()));
			}
			// we didn't find a feature so consume
			// the field and move on
			jp.nextToken();
			jp.skipChildren();
		}
	}

	@SuppressWarnings("unchecked")
	private void readContainment(JsonParser jp, EObject owner,
								 DeserializationContext ctxt, EReference reference,
								 Resource resource, ReferenceEntries entries) throws IOException {

		final Class type = reference.getEReferenceType().getInstanceClass();

		if (type != null && Map.Entry.class.isAssignableFrom(type)) {
			EMap map = (EMap) owner.eGet(reference);

			if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
				while (jp.nextToken() != JsonToken.END_OBJECT) {
					EObject entry = EObjects.createEntry(jp.getCurrentName(), jp.nextTextValue());
					map.add((Map.Entry) entry);
				}
			}

		} else {

			if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
				while (jp.nextToken() != JsonToken.END_ARRAY) {

					try {
						EObject value = deserialize(jp, ctxt, reference);
						if (value != null) {
							EObjects.setOrAdd(owner, reference, value);
							entries.store(resource, value);
						}
					} catch (Exception e) {
						if (resource != null) {
							resource.getErrors().add(new JSONException(e, jp.getCurrentLocation()));
						}
					}
				}
			} else {

				try {
					EObject value = deserialize(jp, ctxt, reference);
					if (value != null) {
						entries.store(resource, value);
					}
					EObjects.setOrAdd(owner, reference, value);
				} catch (Exception e) {
					if (resource != null) {
						resource.getErrors().add(new JSONException(e, jp.getCurrentLocation()));
					}
				}
			}
		}
	}

	private void readReference(JsonParser jp, EObject owner,
							   EReference reference, ReferenceEntries entries,
							   DeserializationContext context) throws IOException {

		if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
			while (jp.nextToken() != JsonToken.END_ARRAY) {
				entries.add(options.referenceDeserializer.deserialize(jp, owner, reference, context));
			}
		} else {
			entries.add(options.referenceDeserializer.deserialize(jp, owner, reference, context));
		}
	}

	private void readAttribute(JsonParser jp, EObject owner,
							   EAttribute attribute, Resource resource,
							   DeserializationContext ctxt) throws IOException {
		final EDataType dataType = (EDataType) owner.eClass().getFeatureType(attribute).getEClassifier();
		if (dataType == null) {
			resource.getErrors().add(new JSONException("Missing feature type", jp.getCurrentLocation()));
			jp.nextToken();
			return;
		}

		if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
			while (jp.nextToken() != JsonToken.END_ARRAY) {
				readSingleAttribute(jp, owner, attribute, resource, dataType, ctxt);
			}
		} else {
			readSingleAttribute(jp, owner, attribute, resource, dataType, ctxt);
		}
	}

	private void readSingleAttribute(JsonParser jp, EObject owner,
									 EAttribute attribute, Resource resource,
									 EDataType dataType,
									 DeserializationContext ctxt) throws IOException {

		final Class type = dataType.getInstanceClass();

		Object value;
		if (type == null || dataType instanceof EEnum ||
				EcorePackage.Literals.EJAVA_CLASS.equals(dataType) ||
				EcorePackage.Literals.EJAVA_OBJECT.equals(dataType)) {

			value = EcoreUtil.createFromString(dataType, jp.getText());

		} else {

			value = ctxt.readValue(jp, type);

		}

		try {
			EObjects.setOrAdd(owner, attribute, value);
		} catch (Exception e) {
			if (resource != null) {
				resource.getErrors().add(new JSONException(e, jp.getCurrentLocation()));
			}
		}
	}

	protected EObject create(JsonParser jp, DeserializationContext context) throws IOException {
		EClass eClass = options.typeDeserializer.deserialize(jp, context);
		if (eClass != null) {
			return EcoreUtil.create(eClass);
		}
		return null;
	}

	@Override
	public Class handledType() {
		return EObject.class;
	}

	@Override
	public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
		prepareContext(ctxt, property);
		return this;
	}

	protected void prepareContext(DeserializationContext ctxt, BeanProperty property) {
		ReferenceEntries entries = (ReferenceEntries) ctxt.getAttribute("entries");
		Resource resource = (Resource) ctxt.getAttribute("resource");

		if (property instanceof ResourceProperty) {
			ResourceProperty p = (ResourceProperty) property;
			resource = p.getResource();
			entries = p.getEntries();
		}

		if (resource == null) {
			resource = new JsonResource(URI.createURI("default"));
			resourceSet.getResources().add(resource);
		}

		if (entries == null) {
			entries = new ReferenceEntries();
		}

		ctxt.setAttribute("entries", entries);
		ctxt.setAttribute("resource", resource);
		ctxt.setAttribute("cache", cache);
		ctxt.setAttribute("resourceSet", resourceSet);
		ctxt.setAttribute("options", options);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy