You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2018/07/27 10:04:02 UTC
[37/53] [partial] brooklyn-ui git commit: Update Brooklyn codebase to
build the newly donated UI
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/47b46402/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js b/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
deleted file mode 100755
index da3e090..0000000
--- a/new/ui-modules/blueprint-composer/app/components/util/d3-blueprint.js
+++ /dev/null
@@ -1,1021 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import * as d3 from 'd3';
-import {PREDICATE_MEMBERSPEC} from './model/entity.model';
-import addIcon from '../../img/icon-add.svg';
-import {ISSUE_LEVEL} from './model/issue.model';
-
-export function D3Blueprint(container) {
- let _svg = d3.select(container).append('svg').attr('class', 'blueprint-canvas');
- let _mirror = _svg.append('path').style('display', 'none');
- let _zoomGroup = _svg.append('g').attr('class', 'zoom-group');
- let _parentGroup = _zoomGroup.append('g').attr('class', 'parent-group');
- let _linkGroup = _parentGroup.append('g').attr('class', 'link-group');
- let _relationGroup = _parentGroup.append('g').attr('class', 'relation-group');
- let _specNodeGroup = _parentGroup.append('g').attr('class', 'spec-node-group');
- let _dropZoneGroup = _parentGroup.append('g').attr('class', 'dropzone-group');
- let _ghostNodeGroup = _parentGroup.append('g').attr('class', 'ghost-node-group');
- let _nodeGroup = _parentGroup.append('g').attr('class', 'node-group');
- let _cloneGroup = _parentGroup.append('g').attr('class', 'clone-group');
-
- let _dragState = {
- dragInProgress: false,
- dragStarted: false,
- clone: null,
- cloneX: 0,
- cloneY: 0,
- };
-
- const _configHolder = {
- nodes: {
- root: {
- rect: {
- class: 'node-root',
- x: -125,
- y: -50,
- width: 250,
- height: 100,
- rx: 50,
- ry: 50,
- },
- text: {
- class: 'node-name',
- width: 250,
- height: 100
- },
- maxNameLength: 18
- },
- child: {
- circle: {
- r: 50,
- class: (d)=>(`node-cluster node-cluster-${d}`)
- },
- image: {
- class: 'node-icon',
- width: 64,
- height: 64,
- x: -32,
- y: -32,
- opacity: 0
- }
- },
- location: {
- rect: {
- x: -50,
- y: -110,
- width: 100,
- height: 50
- },
- image: {
- x: -50,
- y: -110,
- width: 100,
- height: 50,
- opacity: 0
- }
- },
- dropzonePrev: {
- circle: {
- cx: -150,
- r: 30,
- class: 'dropzone dropzone-prev'
- },
- },
- dropzoneNext: {
- circle: {
- cx: 150,
- r: 30,
- class: 'dropzone dropzone-next'
- }
- },
- adjunct: {
- rect: {
- id: (d)=>(`entity-${d._id}`),
- class: 'node-adjunct adjunct entity',
- width: 20,
- height: 20,
- transform: 'scale(0)'
- }
- },
- memberspec: {
- circle: {
- r: 35,
- cx: 0,
- cy: 170,
- class: 'node-spec-entity',
- 'transform-origin': 0
- },
- image: {
- x: -20,
- y: 150,
- width: 40,
- height: 40,
- opacity: 0,
- class: 'node-spec-image',
- 'transform-origin': 0
- }
- },
- buttongroup: {
- line: {
- class: 'link',
- x1: 0,
- x2: 0,
- y1: (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.height / 2 : _configHolder.nodes.child.circle.r),
- y2: (d)=>((isRootNode(d) ? _configHolder.nodes.root.rect.height / 2 : _configHolder.nodes.child.circle.r) + 30),
- },
- circle: {
- class: 'connector',
- r: 6,
- cy: (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.height / 2 : _configHolder.nodes.child.circle.r),
- }
- },
- buttonAdd: {
- circle: {
- r: 20,
- cy: 100
- },
- image: {
- width: 50,
- height: 50,
- x: -25,
- y: 75,
- 'xlink:href': addIcon
- }
- }
- },
- transition: 300,
- grid: {
- itemPerCol: 3,
- gutter: 15
- },
- };
- let _d3DataHolder = {
- nodes: [],
- ghostNodes: [],
- orphans: [],
- links: [],
- relationships: [],
- };
-
- let zoom = d3.zoom().scaleExtent([0.1, 1]).on('zoom', onSvgZoom);
- _svg
- .attr('preserveAspectRatio', 'xMinYMin meet')
- .attr('viewBox', () => {
- return `0 0 ${parseInt(_svg.style('width'))} ${parseInt(_svg.style('height'))}`;
- })
- .on('click', onSvgClick)
- .on('dragover', onSvgDragOver)
- .on('dragleave', onSvgDragLeave)
- .call(zoom);
-
- let pattern = _svg.append('pattern')
- .attr('id', 'fill-has-issues')
- .attr('width', 4)
- .attr('height', 4)
- .attr('patternUnits', 'userSpaceOnUse');
- pattern.append('rect')
- .attr('width', 4)
- .attr('height', 4);
- pattern.append('path')
- .attr('d', 'M1 3h1v1H1V3zm2-2h1v1H3V1z');
-
- let defs = _svg.append('defs');
- let arrowhead = defs.append('marker')
- .attr('id', 'arrowhead')
- .attr('orient', 'auto')
- .attr('markerWidth', 10)
- .attr('markerHeight', 20)
- .attr('markerUnits', 'userSpaceOnUse')
- .attr('refX', 0)
- .attr('refY', 10);
- arrowhead.append('path')
- .attr('d', 'M0,0 V20 L10,10 Z')
- .attr('class', 'arrowhead');
- let arrowheadHighlight = defs.append('marker')
- .attr('id', 'arrowhead-highlight')
- .attr('orient', 'auto')
- .attr('markerWidth', 10)
- .attr('markerHeight', 20)
- .attr('markerUnits', 'userSpaceOnUse')
- .attr('refX', 0)
- .attr('refY', 10);
- arrowheadHighlight.append('path')
- .attr('d', 'M0,0 V20 L10,10 Z')
- .attr('class', 'arrowhead-highlight');
-
- /*****************************
- ** EVENT HANDLERS :: START **
- *****************************/
-
- /**
- * Handles translation and scaling of the zoom group
- */
- function onSvgZoom() {
- _zoomGroup.attr('transform', d3.event.transform);
- }
-
- /**
- * Fires a custom event "click-svg" when the use clicks anywhere on the canvas, except nodes.
- */
- function onSvgClick() {
- if (d3.event.defaultPrevented) return;
- let event = new CustomEvent('click-svg', {});
- container.dispatchEvent(event);
- }
-
- /**
- * Applies the "is-dragging" class to the canvas when a drag is initiated.
- */
- function onSvgDragOver() {
- _svg.classed('is-dragging', true);
-
- if (d3.event.dataTransfer && d3.event.dataTransfer.types.indexOf('entity') === -1) {
- _dropZoneGroup.selectAll('.dropzone-prev').classed('hidden', true);
- _dropZoneGroup.selectAll('.dropzone-next').classed('hidden', true);
- }
- }
-
- /**
- * Removes the "is-dragging" class from the canvas when a drag is finished.
- */
- function onSvgDragLeave() {
- _svg.classed('is-dragging', false);
-
- if (d3.event.dataTransfer && d3.event.dataTransfer.types.indexOf('entity') === -1) {
- _dropZoneGroup.selectAll('.dropzone-prev').classed('hidden', false);
- _dropZoneGroup.selectAll('.dropzone-next').classed('hidden', false);
- }
- }
-
- /**
- * Mouse Enter Event Handler
- *
- * @param {object} node The graph node the mouse is over
- */
- function onGhostOver(node) {
- d3.select(`#ghost-node-${node.data._id} g.buttons`).classed('active', true);
- // show whole group not just buttons
- }
-
- /**
- * Mouse Leave Event Handler
- *
- * @param {object} node The graph node the mouse just left
- */
- function onGhostLeave(node) {
- d3.select(`#ghost-node-${node.data._id} g.buttons`).classed('active', false);
- }
-
- /**
- * Fires a custom event "click-entity" when a graph node is clicked.
- *
- * @param {object} node The node for the clicked entity
- */
- function onEntityClick(node) {
- if (d3.event.defaultPrevented) return;
- d3.event.stopPropagation();
- let event = new CustomEvent('click-entity', {
- detail: {
- entity: node.data || node,
- }
- });
- container.dispatchEvent(event);
- }
-
- /**
- * Fires a custom event "click-add-child" when the plus button is clicked.
- *
- * @param {object} node The node for the entity to add a child to
- */
- function onAddChildClick(node) {
- d3.event.stopPropagation();
- let event = new CustomEvent('click-add-child', {
- detail: {
- entity: node.data,
- }
- });
- container.dispatchEvent(event);
- }
-
- /**
- * Handles the start of a drag operation. Note that this callback is to be used with the internal D3 drag feature.
- *
- * @param node The node for the dragged entity
- */
- function onDragStart(node) {
- if (_dragState.clone) {
- _dragState.clone.remove();
- _dragState.clone = null;
- }
- if (node.depth) { // exclude the root element
- onSvgDragOver();
- hideInvalidDropzones(node);
-
- d3.event.sourceEvent.preventDefault(); // disable browser text selection
- d3.event.sourceEvent.stopPropagation();
- _dragState.dragInProgress = true;
- // at this point, we still don't know if this will be a click or a real drag
- // so we defer the visual effects to the first 'dragging' event
- _dragState.dragStarted = true;
- let entityId = node.data._id;
- let mouseCoords = d3.mouse(_nodeGroup.select(`#entity-${node.data._id}`).node());
- _dragState.cloneX = node.x + _configHolder.nodes.child.circle.r + mouseCoords[0];
- _dragState.cloneY = node.y + _configHolder.nodes.child.circle.r + mouseCoords[1];
- _dragState.clone = _cloneGroup.append('use')
- .attr('xlink:href', function(d) {
- return `#entity-${entityId}` } )
- .attr('opacity', 0)
- .attr('transform', (d)=>(`translate(${_dragState.cloneX}, ${_dragState.cloneY})`));
- }
- }
-
- /**
- * Handles the dragging operation. Note that this callback is to be used with the internal D3 drag feature.
- *
- * @param node The node for the dragged entity
- */
- function onDrag(node) {
- if (_dragState.dragInProgress) {
- if (_dragState.dragStarted) {
- // deferred initialization of visual effects
- _dragState.dragStarted = false;
- hideRelationships(node);
- _dragState.clone.attr('opacity', 0.5);
- }
- if (_dragState.clone) {
- _dragState.cloneX += d3.event.dx;
- _dragState.cloneY += d3.event.dy;
- _dragState.clone.attr('transform', (d)=>(`translate(${_dragState.cloneX}, ${_dragState.cloneY})`));
- }
- }
- }
-
- /**
- * Fires a custom event "move-entity" when an graph node has been dragged and dropped in a valid dropzone.
- * Note that this callback is to be used with the internal D3 drag feature.
- *
- * @param node The node for the dragged entity
- */
- function onDragEnd(node) {
- if (_dragState.dragInProgress) {
- _dragState.dragInProgress = false;
- // Firefox support (target)
- let dropZone = d3.event.sourceEvent.toElement ? d3.event.sourceEvent.toElement : d3.event.sourceEvent.target;
- if (['node-root', 'node-name', 'node-icon', 'node-cluster'].some(className => dropZone.classList.contains(className))) {
- dropZone = dropZone.parentElement;
- }
- if (dropZone && dropZone.classList.contains('dropzone')) {
- let parentId = dropZone.getAttribute('parentId');
- let dropzoneId = dropZone.classList.contains('dropzone-next') || dropZone.classList.contains('dropzone-prev')
- ? dropZone.id
- : `dropzone-self-${parentId}`;
- onDragLeave(node, dropzoneId);
-
- if (node.data._id !== parentId) {
- let event = new CustomEvent('move-entity', {
- detail: {
- nodeId: node.data._id,
- parentId: parentId,
- targetIndex: dropZone.getAttribute('targetIndex'),
- },
- });
- container.dispatchEvent(event);
- }
- } else {
- setTimeout(() => {
- showRelationships();
- showDropzones();
- }, _configHolder.transition);
- }
- }
- if (_dragState.clone) {
- _dragState.clone.remove();
- _dragState.clone = null;
- }
-
- onSvgDragLeave();
- }
-
- /**
- * Applies the class "active" to the currently hovered dropzone, or the dropzone with the optional given ID.
- *
- * @param {*} node the graph node the event refers to
- * @param {String} id Optional Id of the dropzone
- */
- function onDragOver(node, id) {
- if (_dragState.dragInProgress || (d3.event && d3.event.type === 'dragover')) {
- if (!id) {
- id = (d3.event.sourceEvent ? d3.event.sourceEvent : d3.event).target.id;
- }
-
- if (id) {
- _dropZoneGroup.select(`#${id}`).classed('active', true)
- }
- } else {
- onGhostOver(node);
- }
- }
-
- /**
- * Removes the class "active" from the previously hovered dropzone, or the dropzone with the optional given ID.
- *
- * @param {*} node the graph node the event refers to
- * @param {String} id Optional Id of the dropzone
- */
- function onDragLeave(node, id) {
- if (_dragState.dragInProgress ||
- (d3.event && (['end','drop','dragleave'].includes(d3.event.type)))) {
- if (!id) {
- id = (d3.event.sourceEvent ? d3.event.sourceEvent : d3.event).target.id;
- }
-
- if (id) {
- _dropZoneGroup.select(`#${id}`).classed('active', false)
- }
- } else {
- onGhostLeave(node);
- }
- }
-
- /**
- * Fires a custom event "drop-node" when an external node has been dragged and dropped in a valid dropzone.
- * Note that this callback is to be used with the external drag feature, i.e. HTML5
- *
- * @param {String} id Optional Id of the dropzone
- */
- function onExternalDrop(node, id) {
- let dropZone = d3.event.toElement ? d3.event.toElement : d3.event.target;
- if (['node-root', 'node-name', 'node-icon', 'node-cluster'].some(className => dropZone.classList.contains(className))) {
- dropZone = dropZone.parentElement;
- }
- if (dropZone && dropZone.classList.contains('dropzone')) {
- onDragLeave(node, id);
-
- let event = new CustomEvent('drop-external-node', {
- detail: {
- parentId: dropZone.getAttribute('parentId'),
- targetIndex: dropZone.getAttribute('targetIndex'),
- },
- });
- container.dispatchEvent(event);
- }
-
- onSvgDragLeave();
- }
-
- /***************************
- ** EVENT HANDLERS :: END **
- ***************************/
-
- /**
- * Update the graph data
- *
- * @param {object} blueprint The graph
- */
- function update(blueprint, relationships) {
- let tree = d3.tree()
- .nodeSize([_configHolder.nodes.child.circle.r * 6, _configHolder.nodes.child.circle.r * 6])
- .separation((right, left)=> {
- let maxColumnsBeforeExpand = 2;
- let adjuncts = getImportantAdjuncts(left).length;
- let currentCols = Math.floor(adjuncts / _configHolder.grid.itemPerCol) + (adjuncts > 0 && adjuncts % _configHolder.grid.itemPerCol !== 0 ? 1 : 0);
- let additionalCol = currentCols > maxColumnsBeforeExpand ? currentCols - maxColumnsBeforeExpand : 0;
-
- let colWidth = _configHolder.nodes.adjunct.rect.width + 15;
-
- return 1 + (colWidth / (_configHolder.nodes.child.circle.r * 6)) * additionalCol;
- });
- let root = d3.hierarchy(blueprint);
- tree(root);
- _d3DataHolder.nodes = root.descendants();
- _d3DataHolder.links = root.links();
- _d3DataHolder.relationships = relationships;
- return this;
- }
-
- /**
- * Redraw the graph
- */
- function draw() {
- drawLinks();
- drawRelationships();
- drawNodeGroup();
- drawSpecNodeGroup();
- drawGhostNode();
- drawDropZoneGroup();
- return this;
- }
-
- function drawNodeGroup() {
- let nodeData = _nodeGroup.selectAll('g.node')
- .data(_d3DataHolder.nodes, (d)=>(`node-${d.data._id}`));
-
- // Draw group that contains all SVG element: node representation and location/policies/enricher indicators
- // -----------------------------------------------------
- let nodeGroup = nodeData
- .enter()
- .append('g')
- .attr('id', (d)=>(`node-group-${d.data._id}`))
- .attr('class', 'node')
- .classed('node-root', isRootNode)
- .classed('node-child', isChildNode)
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) scale(${isRootNode(d) ? 1 : 0})`))
- .attr('opacity', (d)=> (isRootNode(d) ? 0 : 1));
- nodeData.transition()
- .duration(_configHolder.transition)
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) scale(1)`))
- .attr('opacity', 1);
- nodeData.exit()
- .transition()
- .duration(_configHolder.transition)
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) scale(${isRootNode(d) ? 1 : 0})`))
- .attr('opacity', (d)=> (isRootNode(d) ? 0 : 1))
- .remove();
-
- // Draw the node-entity group that will contain the node representation
- // -----------------------------------------------------
- let entity = nodeGroup.append('g')
- .attr('class', 'node-entity entity')
- .on('click', onEntityClick)
- .on('mouseenter', onGhostOver)
- .on('mouseleave', onGhostLeave)
- .call(d3.drag()
- .on('start', onDragStart)
- .on('drag', onDrag)
- .on('end', onDragEnd));
- nodeData.select('g.node-entity')
- .attr('id', (d)=>(`entity-${d.data._id}`))
- .classed('clustered', (d)=>(d.data.isCluster()))
- .classed('has-warnings', (d)=>(d.data.hasIssues() && d.data.issues.some(issue => issue.level === ISSUE_LEVEL.WARN)))
- .classed('has-errors', (d)=>(d.data.hasIssues() && d.data.issues.some(issue => issue.level === ISSUE_LEVEL.ERROR)))
- .classed('loading', (d)=>(d.data.miscData.get('loading')));
-
- // Draw root node
- appendElements(entity.filter(isRootNode), _configHolder.nodes.root);
- nodeData.filter(isRootNode).select('.node-entity text')
- .text(trimNodeText)
- .transition()
- .duration(_configHolder.transition)
- .text(trimNodeText);
- nodeData.filter(isChildNode).select('.node-entity image')
- .transition()
- .duration(_configHolder.transition)
- .attr('opacity', (d)=>(d.data.hasIcon() ? 1 : 0))
- .attr('xlink:href', (d)=>(d.data.icon));
-
- // Draw child nodes
- appendElement(entity.filter(isChildNode).selectAll('circle').data([2, 1, 0]).enter(), 'circle', _configHolder.nodes.child.circle);
- appendElement(entity.filter(isChildNode), 'image', _configHolder.nodes.child.image);
-
- // Draw location
- // -----------------------------------------------------
- let location = nodeGroup.append('g')
- .attr('class', 'node-location')
- .classed('loading', (d)=>(d.data.miscData.get('loading')));
- nodeData.select('g.node-location')
- .transition()
- .duration(_configHolder.transition)
- .attr('opacity', (d)=>(d.data.hasLocation() ? 1 : 0));
- appendElements(location, _configHolder.nodes.location);
- nodeData.select('g.node-location image')
- .transition()
- .duration(_configHolder.transition)
- .attr('opacity', (d)=>(d.data.miscData.get('locationIcon') ? 1 : 0))
- .attr('xlink:href', (d)=>(d.data.miscData.get('locationIcon')));
-
- // Draw important adjuncts (i.e policies/enrichers)
- // -----------------------------------------------------
- nodeGroup.append('g')
- .attr('class', 'node-adjuncts');
- let adjunctData = nodeData.select('g.node-adjuncts')
- .selectAll('rect.adjunct')
- .data((d)=>(getImportantAdjuncts(d)), (d)=>(`adjunct-${d._id}`));
- adjunctData
- .classed('has-warnings', (d)=>(d.hasIssues() && d.issues.some(issue => issue.level === ISSUE_LEVEL.WARN)))
- .classed('has-errors', (d)=>(d.hasIssues() && d.issues.some(issue => issue.level === ISSUE_LEVEL.ERROR)))
- .classed('loading', (d)=>(d.miscData.get('loading')))
- .on('click', onEntityClick);
- adjunctData.transition()
- .duration(_configHolder.transition)
- .attr('x', (d, i)=>(getGridX(d, i)))
- .attr('y', (d, i)=>(getGridY(d, i)))
- .attr('transform', 'scale(1)')
- .attr('transform-origin', (d, i)=>(getGridItemCenter(d, i)));
- adjunctData.exit()
- .transition()
- .duration(_configHolder.transition)
- .attr('transform', 'scale(0)')
- .remove();
- appendElement(adjunctData.enter(), 'rect', _configHolder.nodes.adjunct.rect);
- }
-
- function drawLinks() {
- let link = _linkGroup.selectAll('line.link')
- .data(_d3DataHolder.links, (d)=>(d.source.data._id + '_to_' + d.target.data._id));
-
- link.enter().insert('line')
- .attr('class', 'link')
- .attr('x1', (d)=>(d.source.x))
- .attr('y1', (d)=>(d.source.y))
- .attr('x2', (d)=>(d.source.x))
- .attr('y2', (d)=>(d.source.y));
- link.transition()
- .duration(_configHolder.transition)
- .attr('x1', (d)=>(d.source.x))
- .attr('y1', (d)=>(d.source.y))
- .attr('x2', (d)=>(d.target.x))
- .attr('y2', (d)=>(d.target.y));
- link.exit()
- .transition()
- .attr('opacity', 0)
- .remove();
- }
-
- /**
- * returns the D3 tree node for a given Entity
- * @param {Entity} entity
- * @return {*} a D3 tree node
- */
- function nodeForEntity(entity) {
- let node = _d3DataHolder.nodes.find((d)=>(d.data._id === entity._id));
- if (!node) {
- throw new Error('Node for Entity ' + entity._id + ' not found');
- }
- return node;
- }
-
- function drawRelationships() {
- showRelationships();
-
- let relationData = _relationGroup.selectAll('.relation')
- .data(_d3DataHolder.relationships, (d)=>(d.source._id + '_related_to_' + d.target._id));
-
- relationData.enter().insert('path')
- .attr('class', 'relation')
- .attr('opacity', 0)
- .attr('from', (d)=>(d.source._id))
- .attr('to', (d)=>(d.target._id));
- relationData.transition()
- .duration(_configHolder.transition)
- .attr('opacity', 1)
- .attr('stroke', 'red')
- .attr('d', function(d) {
- let targetNode = nodeForEntity(d.target);
- let sourceNode = nodeForEntity(d.source);
- let dx = targetNode.x - sourceNode.x;
- let dy = targetNode.y - sourceNode.y;
- let dr = Math.sqrt(dx * dx + dy * dy);
- let sweep = dx * dy > 0 ? 0 : 1;
- _mirror.attr('d', `M ${sourceNode.x},${sourceNode.y} A ${dr},${dr} 0 0,${sweep} ${targetNode.x},${targetNode.y}`);
-
- let m = _mirror._groups[0][0].getPointAtLength(_mirror._groups[0][0].getTotalLength() - _configHolder.nodes.child.circle.r - 20);
-
- dx = m.x - sourceNode.x;
- dy = m.y - sourceNode.y;
- dr = Math.sqrt(dx * dx + dy * dy);
-
- return `M ${sourceNode.x},${sourceNode.y} A ${dr},${dr} 0 0,${sweep} ${m.x},${m.y}`;
- });
- relationData.exit()
- .transition()
- .duration(_configHolder.transition)
- .attr('opacity', 0)
- .remove();
- }
-
- function drawGhostNode() {
- let ghostNodeData = _ghostNodeGroup.selectAll('g.ghost-node')
- .data(_d3DataHolder.nodes, (d)=>(`ghost-node-${d.data._id}`));
- let ghostNode = ghostNodeData
- .enter()
- .append('g')
- .attr('id', (d)=>(`ghost-node-${d.data._id}`))
- .attr('class', 'ghost-node')
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`))
- .on('mouseenter', onGhostOver)
- .on('mouseleave', onGhostLeave);
- ghostNodeData
- .transition()
- .duration(_configHolder.transition)
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
- ghostNodeData.exit().remove();
-
- ghostNode.append('rect')
- .attr('class', 'ghost')
- .attr('width', (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.width : _configHolder.nodes.child.circle.r * 2))
- .attr('height', (d)=>((isRootNode(d) ? _configHolder.nodes.root.rect.height : _configHolder.nodes.child.circle.r * 2) + 80))
- .attr('x', (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.x : -_configHolder.nodes.child.circle.r))
- .attr('y', (d)=>(isRootNode(d) ? _configHolder.nodes.root.rect.y : -_configHolder.nodes.child.circle.r));
-
- let buttonsGroup = ghostNode.append('g')
- .attr('class', 'buttons');
- appendElements(buttonsGroup, _configHolder.nodes.buttongroup);
-
- let buttonAdd = buttonsGroup.append('g')
- .attr('class', 'button button-add')
- .on('click', onAddChildClick);
- appendElements(buttonAdd, _configHolder.nodes.buttonAdd);
- }
-
- function drawDropZoneGroup() {
- showDropzones();
-
- let dropZoneData = _dropZoneGroup.selectAll('g.dropzone-group-node')
- .data(_d3DataHolder.nodes, (d)=>(`dropzone-${d.data._id}`));
-
- let dropZoneGroup = dropZoneData
- .enter()
- .append('g')
- .attr('id', (d)=>(`dropzone-group-node-${d.data._id}`))
- .attr('class', 'dropzone-group-node')
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
- dropZoneData
- .transition()
- .duration(_configHolder.transition)
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
- dropZoneData.exit().remove();
-
- appendElement(dropZoneGroup.filter(isRootNode), 'rect', Object.assign({},
- _configHolder.nodes.root.rect,
- // expand above by 7
- {x: -132, y: -57, rx: 57, ry: 57, width: 264, height: 114, class: 'dropzone dropzone-self'}));
- appendElement(dropZoneGroup.filter(isChildNode), 'circle', Object.assign({},
- _configHolder.nodes.child.circle,
- {transform: (d) => (`scale(${d.data.isCluster() ? 1.5 : 1.15})`), class: 'dropzone dropzone-self'}));
- appendElements(dropZoneGroup.filter(isChildNode), _configHolder.nodes.dropzonePrev);
- appendElements(dropZoneGroup.filter(isChildNode), _configHolder.nodes.dropzoneNext);
-
- dropZoneData.select('.dropzone-self')
- .attr('id', (d)=>(`dropzone-self-${d.data._id}`))
- .attr('parentId', (d) => (d.data._id))
- .attr('targetIndex', -1);
- dropZoneData.select('.dropzone-prev')
- .attr('id', (d)=>(`dropzone-prev-${d.data._id}`))
- .attr('parentId', (d) => (d.data.parent ? d.data.parent._id : ''))
- .attr('targetIndex', (d) => (d.data.parent ? d.data.parent.children.indexOf(d.data) : -1));
- dropZoneData.select('.dropzone-next')
- .attr('id', (d)=>(`dropzone-next-${d.data._id}`))
- .attr('parentId', (d) => (d.data.parent ? d.data.parent._id : ''))
- .attr('targetIndex', (d) => (d.data.parent ? d.data.parent.children.indexOf(d.data) + 1 : -1));
-
- dropZoneData.selectAll('.dropzone')
- // D3 drag
- .on('mouseenter', (d) => onDragOver(d))
- .on('mouseleave', (d) => onDragLeave(d))
- // Palette drag
- .on('dragover', (d) => {
- // We prevent the default to mark this dropzone as valid. Not doing so means that the "drop" event won't be fired.
- d3.event.preventDefault();
- onDragOver(d);
- })
- .on('dragleave', (d) => onDragLeave(d))
- .on('drop', (d) => onExternalDrop(d));
-
- _nodeGroup.selectAll('.node-entity')
- .classed('dropzone', true)
- .attr('parentId', (d)=>(d.data._id))
- .attr('targetIndex', -1)
- // D3 drag
- .on('mouseenter', (d) => (onDragOver(d, `dropzone-self-${d.data._id}`)))
- .on('mouseleave', (d) => (onDragLeave(d, `dropzone-self-${d.data._id}`)))
- // Palette drag
- .on('dragover', (d) => {
- // We prevent the default to mark this dropzone as valid. Not doing so means that the "drop" event won't be fired.
- d3.event.preventDefault();
- onDragOver(d, `dropzone-self-${d.data._id}`);
- })
- .on('dragleave', (d) => (onDragLeave(d, `dropzone-self-${d.data._id}`)))
- .on('drop', (d) => onExternalDrop(d, `dropzone-self-${d.data._id}`));
- }
-
- function drawSpecNodeGroup() {
- let specNodeData = _specNodeGroup.selectAll('g.spec-node')
- .data(_d3DataHolder.nodes.filter((node)=>{
- return !!node.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC);
- }), (d)=>(`spec-node-${d.data._id}`));
- let specNodeGroup = specNodeData
- .enter()
- .append('g')
- .attr('id', (d)=>(`spec-node-${d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC)._id}`))
- .attr('class', 'spec-node')
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y})`));
- specNodeData.transition()
- .duration(_configHolder.transition)
- .attr('transform', (d)=>(`translate(${d.x}, ${d.y}) rotate(${d.data.hasChildren() ? -45 : 0})`));
- specNodeData.exit()
- .transition()
- .duration(_configHolder.transition)
- .attr('opacity', 0)
- .remove();
-
- specNodeGroup.append('polygon')
- .attr('class', 'node-memberspec-link')
- .attr('points', (d)=> {
- let left = _configHolder.nodes.memberspec.circle.r * -1;
- let right = _configHolder.nodes.memberspec.circle.r;
- let bottom = _configHolder.nodes.memberspec.circle.cy;
- return `0,0 ${right},${bottom} ${left},${bottom}`;
- })
- .attr('transform', 'scale(0)');
- specNodeData.select('polygon')
- .transition()
- .duration(_configHolder.transition)
- .attr('transform', 'scale(1)');
-
- let specNode = specNodeGroup.append('g')
- .attr('class', 'node-memberspec entity')
- .attr('id', (d)=>(`entity-${d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC)._id}`))
- .attr('transform-origin', `0 ${_configHolder.nodes.memberspec.circle.cy}`)
- .attr('transform', 'scale(0)')
- .on('click', (d)=>(onEntityClick({data: d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC)})));
- specNodeData.select('.node-memberspec')
- .classed('has-issues', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).hasIssues()))
- .classed('loading', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).miscData.get('loading')));
- specNodeData.select('.node-memberspec')
- .transition()
- .duration(_configHolder.transition)
- .attr('transform', 'scale(1)');
- appendElements(specNode, _configHolder.nodes.memberspec);
- specNodeData.select('image')
- .transition()
- .duration(_configHolder.transition)
- .attr('transform', (d)=>(`rotate(${d.data.hasChildren() ? 45 : 0})`))
- .attr('transform-origin', 0)
- .attr('opacity', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).hasIcon() ? 1 : 0))
- .attr('xlink:href', (d)=>(d.data.getClusterMemberspecEntity(PREDICATE_MEMBERSPEC).icon));
- }
-
- function appendElements(node, definition) {
- let elements = [];
- Object.keys(definition).forEach((tag)=> {
- let properties = definition[tag];
- let element = appendElement(node, tag, properties);
- elements.push(element);
- });
- return elements;
- }
-
- function appendElement(node, tag, properties) {
- let element = node.append(tag);
- Object.keys(properties).forEach((property)=> {
- element.attr(property, properties[property]);
- });
- return element;
- }
-
- /**
- * Calculate the X coordinate of a policies/enricher to place it on the grid
- *
- * @param d the current {entity}
- * @param i the index
- * @returns {number} The X coordinate within the grid
- */
- function getGridX(d, i) {
- let nodeWidth = isRootNode(d.parent) ? _configHolder.nodes.root.rect.width : _configHolder.nodes.child.circle.r * 2;
- let offset = (_configHolder.nodes.adjunct.rect.width + _configHolder.grid.gutter) * Math.floor(i / _configHolder.grid.itemPerCol);
- if (d.parent.isCluster()) {
- offset += 20;
- }
-
- return _configHolder.grid.gutter + (nodeWidth/2) + offset;
- }
-
- /**
- * Calculate the Y coordinate of a policies/enricher to place it on the grid
- *
- * @param d the current {entity}
- * @param i the index
- * @returns {number} The Y coordinate within the grid
- */
- function getGridY(d, i) {
- let nodeHeight = isRootNode(d.parent) ? _configHolder.nodes.root.rect.height : _configHolder.nodes.child.circle.r * 2;
- let columnHeight = _configHolder.nodes.adjunct.rect.height * _configHolder.grid.itemPerCol + _configHolder.grid.gutter * (_configHolder.grid.itemPerCol - 1);
- let offset = nodeHeight > columnHeight ? (nodeHeight - columnHeight) / 2 : 0;
-
- return (_configHolder.nodes.adjunct.rect.height + _configHolder.grid.gutter) * (i%_configHolder.grid.itemPerCol) - (nodeHeight/2) + offset;
- }
-
- /**
- * Calculate the center coordinates of a policies/enricher to place it on the grid
- *
- * @param d the current {entity}
- * @param i the index
- * @returns {number} The center coordinates within the grid
- */
- function getGridItemCenter(d, i) {
- let centerX = getGridX(d, i) + _configHolder.nodes.adjunct.rect.width / 2;
- let centerY = getGridY(d, i) + _configHolder.nodes.adjunct.rect.height / 2;
- return `${centerX} ${centerY}`;
- }
-
- /**
- * Center the graph in the view, considering palette
- */
- function center() {
- let newX = window.innerWidth/2 + (window.innerWidth > 660 ? 220 : 0);
- let newY = _configHolder.nodes.child.circle.r + (_configHolder.nodes.child.circle.r * 2);
- zoom.translateBy(_svg, newX, newY);
- return this;
- }
-
- function trimNodeText(d) {
- if (!d.data.metadata.has('name') || d.data.metadata.get('name').length === 0) {
- return 'New application';
- } else {
- let name = d.data.metadata.get('name');
- return name.length > _configHolder.nodes.root.maxNameLength ? name.substring(0, _configHolder.nodes.root.maxNameLength) + '...' : name
- }
- }
-
- function isRootNode(d) {
- return d.depth === 0;
- }
-
- function isChildNode(d) {
- return d.depth > 0;
- }
-
- function getImportantAdjuncts(d) {
- let adjuncts = d.data.getPoliciesAsArray().concat(d.data.getEnrichersAsArray());
- return adjuncts.filter((adjunct)=>(adjunct.miscData.has('important') && adjunct.miscData.get('important') === true));
- }
-
- function selectNode(id) {
- _svg.selectAll('.entity.selected').classed('selected', false);
- _svg.selectAll('.relation.highlight').classed('highlight', false);
- _svg.select(`#entity-${id}`).classed('selected', true);
- _svg.selectAll(`.relation[from='${id}']`).classed('highlight', true);
- _svg.selectAll(`.relation[to='${id}']`).classed('highlight', true);
- return this;
- }
-
- function unselectNode() {
- _svg.selectAll('.entity.selected').classed('selected', false);
- _svg.selectAll('.relation.highlight').classed('highlight', false);
- return this;
- }
-
- /**
- * Hide the relationships for the dragged entity and its descendants
- * @param node the node for the dragged entity
- */
- function hideRelationships(node) {
- _d3DataHolder.relationships
- .filter(r => r.source.hasAncestor(node.data) || r.target.hasAncestor(node.data))
- .forEach(r => {
- _relationGroup.selectAll(`.relation[from='${r.source._id}'][to='${r.target._id}']`).classed('hidden', true);
- });
- }
-
- /**
- * Shows all relationships
- */
- function showRelationships() {
- _relationGroup.selectAll('.relation').classed('hidden', false);
- }
-
- /**
- * Hide the invalid dropzones for the dragged node
- * @param node the node that is being dragged
- */
- function hideInvalidDropzones(node) {
- _d3DataHolder.nodes
- .filter(d => d.data.hasAncestor(node.data))
- .forEach(d => {
- _dropZoneGroup.selectAll(`#dropzone-group-node-${d.data._id} .dropzone`).classed('hidden', true);
- _dropZoneGroup.selectAll(`.dropzone-prev[parentId='${d.data.parent._id}'][targetIndex='${d.data.parent.children.indexOf(d.data)+1}']`).classed('hidden', true);
- _dropZoneGroup.selectAll(`.dropzone-next[parentId='${d.data.parent._id}'][targetIndex='${d.data.parent.children.indexOf(d.data)}']`).classed('hidden', true);
- });
- }
-
- /**
- * Shows all dropzones
- */
- function showDropzones() {
- _dropZoneGroup.selectAll('.dropzone').classed('hidden', false);
- }
-
- return {
- draw: draw,
- update: update,
- center: center,
- select: selectNode,
- unselect: unselectNode
- };
-}
http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/47b46402/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js
----------------------------------------------------------------------
diff --git a/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js b/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js
deleted file mode 100644
index 7b78a0a..0000000
--- a/new/ui-modules/blueprint-composer/app/components/util/model/dsl.model.js
+++ /dev/null
@@ -1,1023 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import {Entity} from './entity.model';
-
-const FUNCTION_PREFIX = '$brooklyn:';
-
-const TARGETS = [ 'self', 'parent', 'child', 'sibling', 'descendant', 'ancestor', 'root', 'scopeRoot', 'entity', 'component', ];
-
-const UTILITIES = [ 'literal', 'formatString', 'urlEncode', 'regexReplacement' ];
-
-export const FAMILY = {
- CONSTANT: 'constant',
- FUNCTION: 'function',
- REFERENCE: 'reference',
-};
-
-export const KIND = {
- UTILITY : {family: FAMILY.FUNCTION, name: 'utility function'},
- TARGET : {family: FAMILY.FUNCTION, name: 'target entity function'},
- METHOD : {family: FAMILY.FUNCTION, name: 'method'},
- ENTITY : {family: FAMILY.REFERENCE, name: 'entity object'},
- STRING : {family: FAMILY.CONSTANT, name: 'constant string'},
- NUMBER : {family: FAMILY.CONSTANT, name: 'constant number'},
- PORT : {family: FAMILY.CONSTANT, name: 'constant port'},
-};
-
-const ID = new WeakMap();
-const PREV = new WeakMap();
-const NEXT = new WeakMap();
-const PARENT = new WeakMap();
-const PARAMS = new WeakMap();
-const KINDS = new WeakMap();
-const REF = new WeakMap();
-const NAME = new WeakMap();
-const RELATIONSHIPS = new WeakMap();
-const ISSUES = new WeakMap();
-
-const numberRegex = /^[+-]?\d+(?:\.\d*)?|^[+-]?\.\d+/;
-
-const portRangeRegex = /[\d]+\+|[\d]+-[\d]+/;
-
-/**
- * A component of a Dsl expression.
- * This class can represent a constant (e.g., a string or number),
- * an Entity object, or a function call with parameters.
- * Dsl expression are composable, e.g. function parameters are also Dsl expressions.
- * Function calls can also be chained to other function calls.
- */
-export class Dsl {
- /**
- * @param kind
- * @param {string} name
- * @param {Entity} entity
- */
- constructor(kind = KIND.STRING, name, entity) {
- ID.set(this, Math.random().toString(36).slice(2));
- PARAMS.set(this, new Array());
- KINDS.set(this, kind);
- NAME.set(this, name === undefined ? ID.get(this).toString() : name.toString());
- RELATIONSHIPS.set(this, new Array());
- ISSUES.set(this, new Array());
- }
-
- /**
- * The internal entity id
- * @returns {string}
- */
- get _id() {
- return ID.get(this);
- }
-
- /**
- * Get {Dsl} name
- * @returns {string}
- */
- get name() {
- return NAME.get(this);
- }
-
- /**
- * Set {Dsl} name
- * @param {string} name
- */
- set name(name) {
- if (name instanceof String || typeof name === 'string') {
- NAME.set(this, name.toString());
- } else {
- throw new DslError('Cannot set name ... name must be a string: ' + typeof name);
- }
- }
-
- /**
- * Set Entity reference
- * @param {Entity} entity
- */
- set ref(entity) {
- if (entity instanceof Entity) {
- REF.set(this, entity);
- this.kind = KIND.ENTITY;
- } else {
- throw new DslError('Cannot set ref ... ref must be of type Entity');
- }
- }
-
- /**
- * Get Entity reference
- * @return {Entity}
- */
- get ref() {
- return REF.get(this);
- }
-
- /**
- * Get {Dsl} parent
- * @returns {Dsl}
- */
- get parent() {
- return PARENT.get(this);
- }
-
- /**
- * Set {Dsl} parent
- * @param {Dsl} parent
- */
- set parent(parent) {
- if (parent instanceof Dsl) {
- if (PARENT.get(this) !== parent) {
- PARENT.set(this, parent);
- }
- } else {
- throw new DslError('Cannot add parent ... parent must be of type Dsl');
- }
- }
-
- /**
- * Get {Dsl} prev
- * @returns {Dsl}
- */
- get prev() {
- return PREV.get(this);
- }
-
- /**
- * Set {Dsl} prev
- * @param {Dsl} prev
- */
- set prev(prev) {
- if (prev instanceof Dsl) {
- if (PREV.get(this) !== prev) {
- PREV.set(this, prev);
- }
- } else {
- throw new DslError('Cannot set prev ... prev must be of type Dsl');
- }
- }
-
- /**
- * Get {Dsl} next
- * @returns {Dsl}
- */
- get next() {
- return NEXT.get(this);
- }
-
- /**
- * Set {Dsl} next
- * @param {Dsl} next
- */
- set next(next) {
- if (next instanceof Dsl) {
- if (NEXT.get(this) !== next) {
- NEXT.set(this, next);
- }
- } else {
- throw new DslError('Cannot set next ... next must be of type Dsl');
- }
- }
-
- /**
- * Get parameters
- * @return {Array}
- */
- get params() {
- return PARAMS.get(this);
- }
-
- /**
- * Get kind
- * @return
- */
- get kind() {
- return KINDS.get(this);
- }
-
- /**
- * Set kind
- * @param kind
- */
- set kind(kind) {
- if (Object.values(KIND).includes(kind)) {
- KINDS.set(this, kind);
- }
- else {
- throw new DslError('Cannot set kind ... not a valid KIND');
- }
- }
-
- /**
- * Get relationships
- * @return {Array} an array of Entities
- */
- get relationships() {
- return RELATIONSHIPS.get(this);
- }
-
- /**
- * Set relationships
- * @param {Array} relationships an array of Entities
- */
- set relationships(relationships) {
- RELATIONSHIPS.set(this, relationships);
- }
-
- /**
- * Get issues
- * @return {Array}
- */
- get issues() {
- return ISSUES.get(this);
- }
-
- /**
- * Set issues
- * @param {Array} issues
- */
- set issues(issues) {
- ISSUES.set(this, issues);
- }
-
- /**
- * Push param {Dsl}
- * @param {Dsl} param
- * @returns {Dsl}
- */
- param(param) {
- if (param instanceof Dsl) {
- if (this.kind.family !== FAMILY.FUNCTION) {
- throw new DslError('Cannot push param to non-function... Dsl kind is: ' + this.kind.name);
- }
- PARAMS.get(this).push(param);
- param.parent = this;
- return this;
- } else {
- throw new DslError('Cannot push param ... param must be of type Dsl');
- }
- }
-
- /**
- * Pop param
- * @param {string} id
- * @returns {Dsl}
- */
- popParam() {
- if (this.hasParams()) {
- let dsl = PARAMS.get(this).pop();
- PARENT.delete(dsl);
- }
- return this;
- }
-
- /**
- * Has {Dsl} got params
- * @returns {boolean}
- */
- hasParams() {
- return PARAMS.get(this).length > 0;
- }
-
- /**
- * Has {Dsl} got a name
- * @return {boolean}
- */
- hasName() {
- return NAME.has(this);
- }
-
- /**
- * Has {Dsl} got a parent
- * @returns {boolean}
- */
- hasParent() {
- return PARENT.has(this);
- }
-
- /**
- * Has {Dsl} got a next
- * @returns {boolean}
- */
- hasNext() {
- return NEXT.has(this);
- }
-
- /**
- * Has {Dsl} got a prev
- * @returns {boolean}
- */
- hasPrev() {
- return PREV.has(this);
- }
-
- /**
- * Has {Dsl} got a ref
- * @return {boolean}
- */
- hasRef() {
- return REF.has(this);
- }
-
- /**
- * Has {Dsl} got issues
- * @return {boolean}
- */
- hasIssues() {
- return this.issues.length > 0;
- }
-
- /**
- * Retrieves the Dsl for the last chained call
- * @return {Dsl}
- */
- getLastMethod() {
- if (this.next) {
- return this.next.getLastMethod();
- }
- else {
- return this;
- }
- }
-
- /**
- * Chain a function call to this Dsl
- * @param method
- * @return {Dsl}
- */
- chain(method) {
- if (method instanceof Dsl) {
- if (method.kind.family !== FAMILY.FUNCTION) {
- throw new DslError('Cannot push method ... method must be a function');
- }
- let last = this.getLastMethod();
- last.next = method;
- method.prev = last;
- return method;
- } else {
- throw new DslError('Cannot push method ... method must be of type Dsl');
- }
- }
-
- /**
- * Remove the last chained call from this Dsl
- * @return {Dsl}
- */
- popChainedMethod() {
- let last = this.getLastMethod();
- if (last.hasPrev()) {
- NEXT.delete(last.prev);
- PREV.delete(last);
- }
- return this;
- }
-
- /**
- * Get the root node for this Dsl
- * @return {Dsl}
- */
- getRoot() {
- if (this.hasParent()) {
- return this.parent.getRoot();
- }
- else if (this.hasPrev()) {
- return this.prev.getRoot();
- }
- else return this;
- }
-
- /**
- * Equality comparison
- * @param value
- * @return {boolean}
- */
- equals(value) {
- if (value && value instanceof Dsl) {
- try {
- if (this.kind === value.kind) {
- return this.toString() === value.toString();
- }
- } catch (err) {
- }
- }
- return false;
- }
-
- /**
- * Return the DSL representation for this Dsl
- * @return {string}
- */
- toString() {
- let current = this;
- let yaml = current.generate();
- switch (current.kind.family) {
- case FAMILY.FUNCTION:
- yaml = FUNCTION_PREFIX + yaml;
- // fallthrough to next case
- case FAMILY.REFERENCE:
- while (current.hasNext()) {
- current = current.next;
- yaml += '.' + current.generate();
- }
- break;
- default:
- // TODO check if we need unquoted string constants (they're in quotes now)
- }
- return yaml;
- }
-
- toJSON() {
- // note the result of this is serialized, as per JSON.stringify docs for an Object.toJSON:
- // (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)
- // semantics of this method are toReplacedObjectForJsonSerialization _not_ toEncodedJsonString;
- // so we want the toString
- return this.toString();
- }
-
- /**
- * Return a clone for this Dsl
- * @return {Dsl}
- */
- clone() {
- let clone = new Dsl(this.kind, this.name);
- clone.relationships = Array.from(this.relationships);
- clone.issues = Array.from(this.issues);
- if (this.next) {
- clone.next = this.next.clone();
- }
- this.params.forEach(param => clone.param(param.clone()));
-
- return clone;
- }
-
- /**
- * Return the DSL representation for this Dsl component (only)
- * @return {string}
- */
- generate() {
- if (this.kind.family === FAMILY.FUNCTION) {
- return this.name + '(' + this.generateParams() + ')';
- }
- else if (this.kind === KIND.ENTITY) {
- return 'entity("' + this.ref.id + '")';
- }
- else if (this.kind === KIND.STRING) {
- return JSON.stringify(this.name);
- }
- else if (this.kind === KIND.PORT) {
- // In case we get a single port range (i.e. 8080+, not part of a $brooklyn:...)
- // we need to return the value without double quote, otherwise, Brooklyn won't accept it.
- return this.hasParent() || this.hasNext() || this.hasPrev() ? JSON.stringify(this.name) : this.name;
- }
- return this.name;
- }
-
- /**
- * Return the DSL representation for a list of parameters, comma separated
- * @return {string}
- */
- generateParams() {
- return this.params.map(param => param.toString()).join(', ');
- }
-
- /**
- * Recursively visit this Dsl
- * @param func a function to call on each node in the Dsl graph
- */
- visit(func) {
- if (!this.visiting) {
- this.visiting = true;
- func(this);
- this.params.forEach(p => p.visit(func));
- if (this.hasPrev()) {
- this.prev.visit(func);
- }
- if (this.hasNext()) {
- this.next.visit(func);
- }
- this.visiting = false;
- }
- }
-
- /**
- * Retrieve the issues for this Dsl, recursively
- */
- getAllIssues() {
- let allIssues = new Array();
-
- this.visit(dsl => {
- allIssues = allIssues.concat(dsl.issues);
- });
-
- return allIssues;
- }
-
- /**
- * Retrieve the references contained in this Dsl, recursively
- * @return {Array} an array containing either Entities or other Dsls that can be resolved to Entities
- */
- getReferences() {
- let refs = new Set();
-
- this.visit(dsl => {
- if (dsl.kind === KIND.TARGET) {
- if (!dsl.hasPrev() || dsl.prev.kind !== KIND.ENTITY) {
- // only add the leftmost dsl in a chain of entity functions
- refs.add(dsl);
- }
- }
- else if (dsl.kind === KIND.ENTITY) {
- refs.add(dsl.ref);
- }
- });
-
- return Array.from(refs);
- }
-
- /**
- * Retrieve the Entities referenced by this Dsl
- * @param {Entity} entity the base Entity, used to resolve relative references
- * @param {function} entityResolver a function to resolve an entity from an ID
- * @return {Array} the Entities referenced by this Dsl
- */
- getRelationships(entity, entityResolver) {
- let references = this.getReferences();
- let entitySet = new Set();
- for (const ref of references) {
- if (ref instanceof Entity) {
- entitySet.add(ref);
- }
- else if (ref instanceof Dsl) {
- let resolvedEntity = ref.resolveEntity(entity, entityResolver);
- if (resolvedEntity && resolvedEntity !== entity) { // don't add relationships to self
- entitySet.add(resolvedEntity);
- }
- }
- else {
- throw new Error('Invalid value type in dsl references: ' + typeof ref);
- }
- }
- return Array.from(entitySet);
- }
-
- /**
- * Resolve the Entity referenced by this Dsl
- * @param {Entity} entity the base entity to resolve relative targets against
- * @param {function} entityResolver a function to resolve an Entity from an ID
- * @return {Entity}
- */
- resolveEntity(entity, entityResolver) {
- let dsl = this;
- let curr = entity;
- while (dsl.kind === KIND.TARGET) {
- switch (dsl.name) {
- case 'self':
- break;
- case 'parent':
- if (!curr.parent) {
- this.issues.push('The entity with ID <code>' + curr.id + '</code> does not have a parent');
- }
- curr = curr.parent;
- break;
- case 'child':
- case 'sibling':
- case 'descendant':
- case 'ancestor':
- case 'entity':
- case 'component': // component can have 1 or 2 params
- let name = dsl.params[dsl.params.length - 1].name;
- let resolvedEntity = entityResolver(name);
- if (resolvedEntity === null) {
- this.issues.push('The reference ID <code>' + name + '</code> does not exist');
- }
- curr = resolvedEntity;
- break;
- case 'root':
- case 'scopeRoot':
- curr = this.resolveRoot(curr);
- break;
- }
- if (dsl.hasNext()) {
- // follow the call chain
- dsl = dsl.next;
- }
- else {
- break;
- }
- }
- return curr;
- }
-
- /**
- * Utility method to get the root from an Entity
- * @param {Entity} e an Entity
- * @return {Entity} the root Entity
- */
- resolveRoot(e) {
- while (e.hasParent()) {
- e = e.parent;
- }
- return e;
- }
-
-}
-
-/**
- * A parser for Dsl expressions.
- */
-export class DslParser {
- /**
- * @param {*} s a Dsl expression to parse, e.g. a string
- */
- constructor(s) {
- this.s = s;
- }
-
- /**
- * Parse this expression, or throw if it is malformed.
- * @param {Entity} entity the base Entity to resolve relative references from
- * @param {function} entityResolver a function to resolve an entity from an ID
- * @return {Dsl} the Dsl object representing this expression
- */
- parse(entity, entityResolver) {
- if (this.s instanceof String || typeof this.s === 'string') {
- return this.parseString(this.s.toString().trim(), entity, entityResolver);
- }
- // else ... TODO support JSON objects (for YAML syntax)
- else {
- throw new DslError("Unable to parse: " + typeof this.s);
- }
- }
-
- /**
- * Parse an expression in string form, or throw.
- * @param {string} s the expression to parse
- * @param {Entity} entity the base Entity to resolve relative references from
- * @param {function} entityResolver a function to resolve an entity from an ID
- * @return {Dsl} the Dsl object representing the expression in s
- */
- parseString(s, entity, entityResolver) {
- let t = new Tokenizer(s);
- let dsl = this.expression(t);
- t.skipWhitespace();
- if (!t.atEndOfInput()) {
- throw new DslError('EXPRESSION followed by spurious content: ' + t.toJSON());
- }
- if (entity && entityResolver) {
- dsl.relationships = dsl.getRelationships(entity, entityResolver);
- }
- return dsl;
- }
-
- /**
- * Parse a Dsl expression, or throw.
- * EXPRESSION ::= FUNCTION_CHAIN | CONSTANT
- * @param {Tokenizer} t the current Tokenizer
- * @return {Dsl} the Dsl object representing the expression
- */
- expression(t) {
- if (t.peek(FUNCTION_PREFIX)) {
- t.next(FUNCTION_PREFIX);
- return this.functionChain(t);
- }
- else {
- return this.constant(t);
- }
- }
-
- /**
- * Parse a Dsl function call chain, or throw.
- * FUNCTION_CHAIN ::= FUNCTION_CALL { "." FUNCTION_CALL }*
- * @param {Tokenizer} t the Tokenizer
- * @return {Dsl} the Dsl object representing a function call chain
- */
- functionChain(t) {
- let func = this.functionCall(t);
- while (t.peek('.')) {
- t.next('.');
- func.chain(this.functionCall(t));
- }
- return func;
- }
-
- /**
- * Parse a Dsl function call, or throw.
- * FUNCTION_CALL ::= IDENTIFIER "(" [ EXPRESSION {"," EXPRESSION}* ] ")"
- * @param {Tokenizer} t the Tokenizer
- * @return {Dsl} the Dsl object representing the function call
- */
- functionCall(t) {
- let name = t.nextIdentifier();
- let dsl = new Dsl(this.functionKind(name), name);
- t.next('(');
- while (!t.atEndOfInput()) {
- if (t.peek(')')) {
- // end of params
- break;
- }
- dsl.param(this.expression(t));
- if (t.peek(',')) {
- t.next(',');
- if (t.atEndOfInput()) {
- throw new DslError('Expected: EXPRESSION but found: end-of-input');
- }
- }
- }
- t.next(')');
- return dsl;
- }
-
- /**
- * Parse a Dsl constant, e.g. a string or a number, or throw.
- * @param {Tokenizer} t the Tokenizer
- * @return {Dsl} the Dsl object representing the constant
- */
- constant(t) {
- if (t.peek('"')) {
- // a string in double quotes
- return new Dsl(KIND.STRING, JSON.parse(t.nextQuotedString()));
- }
- else if (t.peek('\'')) {
- // a string in single quotes (YAML syntax)
- let s = t.nextSingleQuotedString();
- // convert to double quoted string (JSON syntax)
- s = '"' + s.replace(/^'/, '').replace(/'$/, '').replace(/"/g, '\\\"').replace(/''/g, "'") + '"';
- return new Dsl(KIND.STRING, JSON.parse(s));
- }
- else if (t.peekPortRange()) {
- // a port range
- return new Dsl(KIND.PORT, t.nextPortRange());
- }
- else if (t.peekNumber()) {
- // a floating point number
- return new Dsl(KIND.NUMBER, t.nextNumber());
- }
- throw new DslError('Expected: CONSTANT but found: ' + t.toJSON());
- }
-
- /**
- * Find the most appropriate KIND for a function, by comparing
- * its name to all known TARGETS and UTILITIES.
- * @param name the function name
- * @return {*} one of KIND.TARGET, KIND.UTILITY, KIND.METHOD
- */
- functionKind(name) {
- if (TARGETS.includes(name)) {
- return KIND.TARGET;
- }
- else if (UTILITIES.includes(name)) {
- return KIND.UTILITY;
- }
- return KIND.METHOD;
- }
-}
-
-/**
- * A string tokenizer. Retrieves tokens, such as symbols, characters,
- * quoted strings, numbers, from a string.
- * It is used by the DslParser to parse complex Dsl expressions.
- */
-export class Tokenizer {
- /**
- * @param {string} s the string to tokenize
- */
- constructor(s) {
- // this.s contains the current input buffer
- this.s = s.trim();
- }
-
- /**
- * Return <code>true</code> if there are no more characters in the input.
- * @return {boolean}
- */
- atEndOfInput() {
- return this.s.length === 0;
- }
-
- /**
- * Fetch one identifier, such as a function name, or throw.
- * @return {string}
- */
- nextIdentifier() {
- this.skipWhitespace();
- let spl = this.s.split(/\s|[^$A-Za-z0-9_]/, 1);
- if (spl.length > 1) {
- this.s = spl[1];
- return spl[0];
- }
- else if (spl.length === 1) {
- this.skipChars(spl[0].length);
- return spl[0];
- }
- else {
- throw new DslError('Expected IDENTIFIER but found: ' + this.toJSON());
- }
- }
-
- /**
- * Skip whitespace from the input.
- */
- skipWhitespace() {
- // input was right trimmed, so this is effectively a left trim.
- this.s = this.s.trim();
- }
-
- /**
- * Fetch the requested symbol, or throw.
- * @param {string} sym one or more non-whitespace characters, e.g.,
- * a reserved keyword or a special character.
- * @return {string}
- */
- next(sym) {
- sym = sym.trim();
- if (sym.length === 0) {
- throw new DslError("Empty symbol");
- }
- this.skipWhitespace();
- if (this.s.startsWith(sym)) {
- this.skipChars(sym.length);
- this.skipWhitespace();
- return sym;
- }
- else {
- throw new DslError('Expected: "' + sym + '" but found: ' + this.toJSON());
- }
- }
-
- /**
- * Fetch a string in double quotes, or throw.
- * @return {string}
- */
- nextQuotedString() {
- let str = this.next('"');
- let prev = '';
- let curr = '';
- let terminated = false;
- while (!this.atEndOfInput()) {
- curr = this.nextChar();
- str += curr;
- if (prev !== '\\' && curr === '"') {
- // end of string
- terminated = true;
- break;
- }
- prev = curr;
- }
- if (!terminated) {
- throw new DslError('Unterminated quoted string');
- }
- return str;
- }
-
- /**
- * Fetch a string in single quotes (YAML syntax), or throw.
- * @return {string}
- */
- nextSingleQuotedString() {
- let str = this.next('\'');
- let prev = '';
- let curr = '';
- let terminated = false;
- let doubles = false;
- while (!this.atEndOfInput()) {
- curr = this.nextChar();
- str += curr;
- if (curr === '\'') {
- if (prev === '\'' && doubles === false) {
- doubles = true;
- }
- else if (this.peek('\'') === false) {
- // end of string
- terminated = true;
- break;
- }
- }
- else {
- doubles = false;
- }
- prev = curr;
- }
- if (!terminated) {
- throw new DslError('Unterminated quoted string');
- }
- return str;
- }
-
- /**
- * Fetch a literal number, or throw.
- * @return {number}
- */
- nextNumber() {
- let mm = this.s.match(numberRegex);
- if (mm === null) {
- throw new DslError('Expected: NUMBER but found: ' + this.toJSON());
- }
- this.skipChars(mm[0].length);
- return Number(mm[0]);
- }
-
- /**
- * Fetch a port range, or throw
- * Valid port ranges are, e.g., <code>8080+</code> or <code>1024-32767</code>
- * @return {string}
- */
- nextPortRange() {
- let mm = this.s.match(portRangeRegex);
- if (mm === null) {
- throw new DslError('Expected: PORT_RANGE but found: ' + this.toJSON());
- }
- this.skipChars(mm[0].length);
- return mm[0];
- }
-
- /**
- * Fetch the next character in the input buffer, or throw.
- * @return {string} the next character
- */
- nextChar() {
- if (!this.atEndOfInput()) {
- let c = this.s.charAt(0);
- this.skipChars(1);
- return c;
- }
- else {
- throw new DslError('Expected CHAR but found: end-of-input');
- }
- }
-
- /**
- * Consume characters from the start of the input buffer
- * @param num the number of characters to skip
- */
- skipChars(num) {
- this.s = this.s.substring(num);
- }
-
- /**
- * Return <code>true</code> if a call to <code>next(sym)</code> would succeed.
- * @param {string} sym one or more characters, e.g.,
- * a reserved keyword or a special character.
- * @return {boolean}
- */
- peek(sym) {
- return this.s.startsWith(sym);
- }
-
- /**
- * Return <code>true</code> if a call to <code>nextPortRange()</code> would succeed.
- * @return {boolean}
- */
- peekPortRange() {
- return this.s.search(portRangeRegex) >= 0;
- }
-
- /**
- * Return <code>true</code> if a call to <code>nextNumber()</code> would succeed.
- * @return {boolean}
- */
- peekNumber() {
- return this.s.search(numberRegex) >= 0;
- }
-
- /**
- * Return a JSON of the current buffer.
- * Mainly for diagnostic purposes.
- * @return {string}
- */
- toJSON() {
- // FIXME - this is not compatible with JSON.stringify which expects _unserialized_ object
- // ie calling JSON.stringify(this) will result in _twice_ escaped JSON.
- // but need to review uses of this method (in case there is a caller elsewhere who expects
- // valid JSON). [as noted above in other toJSON, the method name is horribly ambiguous!]
- return JSON.stringify(this.s);
- }
-}
-
-export class DslError extends Error {
-
- constructor(message, options = {}) {
- super(message);
- this.name = 'DslError';
- this.message = message;
- this.id = options.id || 'general-error';
- this.data = options.data || null;
- if (typeof Error.captureStackTrace === 'function') {
- Error.captureStackTrace(this, this.constructor);
- } else {
- this.stack = (new Error(message)).stack;
- }
- }
-}