fr.inria.atlanmod.discoverer.JsonDiscoverer.java Source code

Java tutorial

Introduction

Here is the source code for fr.inria.atlanmod.discoverer.JsonDiscoverer.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2013
 * 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:
 *    Javier Canovas (javier.canovas@inria.fr) 
 *******************************************************************************/

package fr.inria.atlanmod.discoverer;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

/**
 * Main class to discover/refine metamodels (ecore file) from json definitions (json file).
 * This implementation does not depend on Xtext
 * 
 * @author Javier Canovas (javier.canovas@inria.fr)
 *
 */
public class JsonDiscoverer {
    HashMap<String, EClass> eClasses = new HashMap<String, EClass>();

    private final static Logger LOGGER = Logger.getLogger(JsonDiscoverer.class.getName());

    public JsonDiscoverer() {
        Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("ecore", new EcoreResourceFactoryImpl());
        Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi", new XMIResourceFactoryImpl());

        LOGGER.setLevel(Level.OFF);
    }

    /**
     * Launches the metamodel discoverer from a JSON model. The method received a JsonSource 
     * element which includes the set of JSON definitions to be considered. 
     * 
     * At the end, the discovered metamodel is returned and also stored in the JsonSource
     * received as param
     * 
     * @param source
     * @return Epackage (Ecore model)
     */
    public EPackage discoverMetamodel(JsonSource source) {
        if (source == null)
            throw new IllegalArgumentException("Source cannot be null");
        else if (source.getJsonData().size() == 0)
            throw new IllegalArgumentException("The source must include, at least, one JSON definition.");

        List<JsonObject> elements = source.getSourceDigested();

        LOGGER.fine("[discoverMetamodel] Received " + elements.size() + " json objects to discover");

        String sourceName = (source.includesInput()) ? source.getName() + "Input" : source.getName();

        for (JsonObject jsonObject : elements) {
            discoverMetaclass(sourceName, jsonObject);
        }

        // Default package
        EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
        ePackage.setName(source.getName());
        ePackage.setNsURI("http://fr.inria.atlanmod/discovered/" + source.getName());
        ePackage.setNsPrefix("disco" + source.getName().charAt(0));
        ePackage.getEClassifiers().addAll(geteClasses().values());
        source.setMetamodel(ePackage);

        // Calculating coverage
        AnnotationHelper.INSTANCE.calculateCoverage(ePackage);

        return ePackage;
    }

    /**
     * Refines an existing metamodel with new JSON definitions coming from a new JsonSource
     * 
     * @param toRefine
     * @param source
     * @return
     */
    public EPackage refineMetamodel(EPackage toRefine, JsonSource source) {
        for (EClassifier eClassifier : toRefine.getEClassifiers()) {
            if (eClassifier instanceof EClass) {
                EClass eClass = (EClass) eClassifier;
                eClasses.put(eClass.getName(), eClass);
                LOGGER.fine("added " + eClass.getName());
            }
        }
        return discoverMetamodel(source);
    }

    /**
     * Discover the metaclass for a JsonObject
     * 
     * @param id Unique identifier for the JsonObject 
     * @param jsonObject the JsonObject
     * @return Discovered EClass
     */
    private EClass discoverMetaclass(String id, JsonObject jsonObject) {
        EClass eClass = eClasses.get(id);
        if (eClass != null) {
            LOGGER.finer("[discoverMetaclass] Refining " + id);
            eClass = refineMetaclass(eClass, jsonObject);
        } else {
            LOGGER.finer("[discoverMetaclass] Creating " + id);
            eClass = createMetaclass(id, jsonObject);
        }
        return eClass;
    }

    /**
     * Creates a new metaclass form scratch. Takes a jsonobject as input and 
     * an identifier.
     * 
     * @param id
     * @param jsonObject
     */
    private EClass createMetaclass(String id, JsonObject jsonObject) {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName(id);
        AnnotationHelper.INSTANCE.increaseTotalFound(eClass);

        eClasses.put(id, eClass);

        LOGGER.fine("[createMetaclass] Metaclass created with name " + id);
        Iterator<Map.Entry<String, JsonElement>> pairs = jsonObject.entrySet().iterator();
        LOGGER.finer("[createMetaclass] Iterating over " + jsonObject.entrySet().size() + " pairs");
        while (pairs.hasNext()) {
            Map.Entry<String, JsonElement> pair = pairs.next();

            String pairId = pair.getKey();
            JsonElement value = pair.getValue();

            createStructuralFeature(pairId, value, 1, eClass);
        }
        return eClass;
    }

    /**
     * Refines the attributes and references of an existing eclass
     * from a new JsonObject definition. 
     * 
     * @param eClass
     * @param JsonObject
     */
    private EClass refineMetaclass(EClass eClass, JsonObject jsonObject) {
        LOGGER.fine("[refineMetaclass] Refining metaclass " + eClass.getName());
        AnnotationHelper.INSTANCE.increaseTotalFound(eClass);

        Iterator<Map.Entry<String, JsonElement>> pairs = jsonObject.entrySet().iterator();
        while (pairs.hasNext()) {
            Map.Entry<String, JsonElement> pair = pairs.next();

            String pairId = pair.getKey();
            JsonElement value = pair.getValue();

            EStructuralFeature eStructuralFeature = null;
            if ((eStructuralFeature = eClass.getEStructuralFeature(pairId)) != null) {
                AnnotationHelper.INSTANCE.increaseTotalFound(eStructuralFeature);
                if (eStructuralFeature instanceof EAttribute) {
                    EAttribute eAttribute = (EAttribute) eStructuralFeature;
                    if (eAttribute.getEType() != mapType(pairId, value)) {
                        LOGGER.fine("[refineMetaclass] Attribute " + eAttribute.getName()
                                + " typed to String due to conflicts");
                        eAttribute.setEType(EcorePackage.Literals.ESTRING);
                    } else {
                        LOGGER.fine("[refineMetaclass] No conflicts with attribute " + eAttribute.getName());
                    }
                } else if (eStructuralFeature instanceof EReference) {

                }
            } else {
                createStructuralFeature(pairId, value, 0, eClass);
            }
        }

        return eClass;
    }

    /**
     * Creates a new structuralFeature out from a pairId/Value
     * 
     * @param pairId
     * @param value
     * @param eClass
     */
    private void createStructuralFeature(String pairId, JsonElement value, int lowerBound, EClass eClass) {
        EStructuralFeature eStructuralFeature = null;
        EClassifier type = mapType(pairId, value);

        if (type instanceof EDataType) {
            eStructuralFeature = EcoreFactory.eINSTANCE.createEAttribute();
        } else if (type instanceof EClass) {
            eStructuralFeature = EcoreFactory.eINSTANCE.createEReference();
            ((EReference) eStructuralFeature).setContainment(true);
        }

        if (value.isJsonArray()) {
            eStructuralFeature.setUpperBound(-1);
        }

        if (eStructuralFeature != null) {
            eStructuralFeature.setName(pairId);
            eStructuralFeature.setLowerBound(lowerBound);
            eStructuralFeature.setEType(mapType(pairId, value));
            AnnotationHelper.INSTANCE.increaseTotalFound(eStructuralFeature);
            eClass.getEStructuralFeatures().add(eStructuralFeature);
            LOGGER.fine("[createStructuralFeature] " + eStructuralFeature.getClass().getSimpleName()
                    + " created with name " + pairId + " type " + eStructuralFeature.getEType().getName()
                    + " and lower bound " + lowerBound);

            if (type instanceof EClass) {
                //create eOpposite
                EReference eOppositeFeature = EcoreFactory.eINSTANCE.createEReference();
                eOppositeFeature.setName(eClass.getName().toLowerCase());
                eOppositeFeature.setLowerBound(1);
                eOppositeFeature.setUpperBound(1);
                eOppositeFeature.setEType(eClass);
                eOppositeFeature.setEOpposite(((EReference) eStructuralFeature));
                EClass opposite = (EClass) type;
                opposite.getEStructuralFeatures().add(eOppositeFeature);
                ((EReference) eStructuralFeature).setEOpposite(eOppositeFeature);
            }
        }
    }

    /**
     * Maps json types into ecore types
     * 
     * @param id
     * @param value
     * @return
     */
    private EClassifier mapType(String id, JsonElement value) {
        if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) {
            return EcorePackage.Literals.ESTRING;
        } else if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isNumber()) {
            return EcorePackage.Literals.EINT;
        } else if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isBoolean()) {
            return EcorePackage.Literals.EBOOLEAN;
        } else if (value.isJsonArray()) {
            JsonArray arrayValue = value.getAsJsonArray();
            if (arrayValue.size() > 0) {
                EClassifier generalArrayType = mapType(digestId(id), arrayValue.get(0));
                for (int i = 1; i < arrayValue.size(); i++) {
                    JsonElement arrayElement = arrayValue.get(i);
                    EClassifier arrayType = mapType(digestId(id), arrayElement);
                    if (generalArrayType != arrayType) {
                        LOGGER.finer(
                                "[mapType] Detected array multi-typed, using fallback type (String) for " + id);
                        return EcorePackage.Literals.ESTRING;
                    }
                }
                return generalArrayType;
            }
        } else if (value.isJsonObject()) {
            return discoverMetaclass(digestId(id), value.getAsJsonObject());
        }
        LOGGER.finer("[mapType] Type not discovererd for " + id);
        return EcorePackage.Literals.ESTRING;
    }

    private String digestId(String id) {
        String result = id;
        if (result.length() > 1 && result.endsWith("s"))
            result = result.substring(0, result.length() - 1);
        result = result.substring(0, 1).toUpperCase() + result.substring(1, result.length());
        return result;
    }

    private HashMap<String, EClass> geteClasses() {
        return eClasses;
    }
}