Source

graph-environment.js

// Copyright (c) 2019 datagraph gmbh

/**
 @overview

 @typedef {Object} Context
 */


/**
 The abstract GraphEnvironment class defines the interface to graphs
 and their elements.
 @abstract
 @property context - A dictionary which maps bi-directionally  between property names and IRI
 @property module - A dictionary which maps class names to classes.
 */

export class GraphEnvironment {
  /**
   @param {Object} options
   @param {(string|URL|Context)} options.context
   @param {Object} options.module
   */
  constructor(options = {}) {
    //this.context = null;
    this.resolveContext(options['context']);
    this.module = options['module'] || {};
  }

  /**
   Return the base IRI from this environment's context.
   @return {string} - the base IRI
   */
  get baseIRI() {
    return (this.context['@base'] || null);
  }

  /**
   Accept a context designator, retrieve if necessary, expand all definitions,
   and bind the environment's context property to it.
   @param {(string|URL|Context)} context - The context to resolve.
   */
  resolveContext(context) {
    var thisEnv = this;
    var expandContext = function(context) {
      var base = context['@base'];
      Object.values(context).forEach(function(def) {
        var uri = def['@id'];
        if (uri) {
          def['@id'] = (base ? new URL(uri, base).href: new URL(uri).href);
        }
      });
      return (context);
    }
    var fetchContext = function(context) {
      fetch(context).then(function(response) {
        thisEnv.context = expandContext(response.json());
      })
    }
    if (context) {
      switch (typeof(context)) {
      case 'object':
        if (context instanceof URL) {
          fetchContext(context.href);
        } else {
          this.context = expandContext(context);
        }
        break;
      case 'string':
        fetchContext(context);
        break;
      default:
        throw (new TypeError(`resolveContext: invalid context: ${context}` ) );
      }
    } else {
      this.context = {};
    }
  }

  /**
   Return the identifier for the root node of the given Graph.
   @param {Graph} graph
   */
  graphResourceID (graph) {
    throw (new Error('GraphEnvironment.fieldResourceID must be implemented'));
  }

  /**
   Return the identifers for all nodes in the given Graph.
   @param {Graph} graph
   */
  graphResourceIDs (graph) {
    throw (new Error('GraphEnvironment.fieldResourceIDs must be implemented'));
  }

  /**
   Given a property identifier, return the definition from the environment's context.
   @param {(string|URL)} identifier - An identifier present in the context
   @return {PropertyDefinition}
   @todo The definition should be a standard JavaScript property descriptor
   @todo Change name to getPropertyDescriptor
   */
  fieldDefinition(identifier) {
    var def;
    switch (typeof(identifier)) {
    case 'string': // field name
      return (this.context[name] || null);
    case 'object': // url
      var namestring = identifier.lexicalForm;
      def = this.context[namestring];
      if (def) {
        return (def);
      } else {
        var localPart = identifier.localPart();
        def = this.context[localPart];
        if (def) {
          this.context[namestring] = def;
          return (def);
        } else {
          return (this.context[namestring] = {});
        }
      }
    default:
      return (null);
    }
  }

  /**
   Given a property identifier, return the type from its definition 
   @todo Change name to getPropertyType
   */
  fieldType(identifier) {
    var def = this.fieldDefinition(identifier);
    return (def ? def['@type'] : null)
  }

  /**
   Given an IRI, return the property name associated with it in the environment.
   If none is present in the context, add a definition which specifies the IRI leaf as the name.
   The first probe searches the context for a property definition which specifies the iri as its @id.
   That result is then cached for future references.
   @oaram {(string|URL)} uri
   */
  findIdentifierName(uri) {
    // console.log("fin", uri);
    var uriNamestring = null;
    switch (typeof(uri)) {
    case 'string': // iri as string
      uriNamestring = uri;
      break;
    case 'object': // url
      uriNamestring = uri.lexicalForm;
      break;
    default:
      return (null);
    }
    var fieldName = this.context[uriNamestring];
    if (! fieldName) {
      for (var name in this.context) {
        var def = this.context[name];
        var id = def['@id'];
        if (id == uriNamestring) {
          fieldName = name;
          this.context[uriNamestring] = name;
          break;
        }
      }
    }
    if (! fieldName) {
      fieldName = this.context[uriNamestring] = predicateLeaf(uri);
    }

    // console.log("fin=", fieldName);
    return ( fieldName );
  }

  /**
   Given a property name, return the IRI associated with it in the environment.
   @param {string} name
   */
  findNameIdentifier(name) {
    // console.log('fni');
    // console.log(this);
    // console.log(this.context);
    var uri = null;
    var def = this.context[name];
    // console.log('fni: ' + name);
    if (def) {
      uri = def['@id'];
      // console.log(uri);
      return (uri || null);
    }
    // console.log(uri);
    return (uri);
  }

  /**
   Given a Graph, extract the first subject term, extract its description and instantiate it.
   @abstract
   */
  computeGraphObject(graph, identifier) {
    throw (new Error('GraphEnvironment.computeGraphObject must be implemented'));
  }
  /**
   Given a Graph and a list of identifiers, extract their descriptions and instantiate them.
   @param {Graph} graph
   @param {Array} identifiers - The sought identifiers
   @abstract
   */
  computeGraphObjects(graph, identifiers) {
    throw (new Error('GraphEnvironment.computeGraphObjects must be implemented'));
  }
  /**
   @param {Object} object
   @abstract
   */
  computeObjectGraph(object) {
    throw (new Error('GraphEnvironment.computeObjectGraph must be implemented'));
  }

  /**
   Given subject, predicate, object and graph terns, construct and return a statement
   @abstract
   @param {Node} subject
   @param {NamedNode} predicate
   @param {Term} object
   @param {Node} [graph]
   */
  createStatement(subject, predicate, object, context) {
    throw (new Error('GraphEnvironment.createStatement must be implemented'));
  }
  /**
   @abstract
   */
  createGraph(statements, options) {
    throw (new Error('GraphEnvironment.createGraph must be implemented'));
  }
  /**
   @abstract
   */
  createLiteral(value, options) {
    throw (new Error('GraphEnvironment.createLiteral must be implemented'));
  }
  /**
   @abstract
   */
  createAnonymousNode(label) {
    throw (new Error('GraphEnvironment.createAnonymousNode must be implemented'));
  }
  /**
   @abstract
   */
  createNamedNode(identifier) {
    throw (new Error('GraphEnvironment.createIdentifiedNode must be implemented'));
  }

  /**
   Given a class name, the instance identifier and an initial state,
   instantiate the object, assign the initial state, create its proxy and return that.
   @param {string} className
   @param {string} identifier
   @param {Object} [state]
   */
  createObject(className, identifier, state = {}) {
    // console.log('createObject', className, 'prototype', className.prototype)
    var classInstance = this.module[className];
    // console.log('class', classInstance);
    // console.log('state', state);
    var defs = {};
    if (classInstance) {
      var instance = Object.create(classInstance.prototype, defs);
      instance.identifier = identifier;
      instance.initializeState(instance.stateClean);
      // apply state after initialization, but before proyxing
      Object.entries(state).forEach(function([entryKey, entryValue]) {
        instance[entryKey] = entryValue;
      });
      var proxy = instance.createProxy();  // do not test, just require the operator
      // console.log('graph-environment.createObject: instance', typeof(instance), instance);
      // console.log('graph-environment.createObject: proxy', typeof(proxy), proxy);
      // console.log('graph-environment.createObject: instance.constructor', instance.constructor);
      // console.log('graph-environment.createObject: instance.constructor', proxy.constructor);
      // console.log('graph-environment.createObject: state', instance._state);
      return( proxy );
    } else {
      console.log(`graph-environment.createObject: class not found '${className}'`);
      return (state);
    }
  } 
}

/**
 Given an IRI return the last element of its path.
 @param {(string|URL)} url
 @returns {string}
 */
export function predicateLeaf(url) {
  var asURL = ( (url instanceof URL) ? url : new URL(url.toString()))
  return ( (asURL.hash.length > 0) ? asURL.hash.slice(1) : asURL.pathname.split('/').pop() );
}