From f880caa24399aedd816f8bd00d12493a7888de1c Mon Sep 17 00:00:00 2001 From: Payam Meyer Date: Tue, 14 Mar 2017 14:22:14 -0400 Subject: [PATCH] Finally fixed the highlighting. Some more fixes and added items to the side nav. --- .../java/org/lumongo/ui/client/LumongoUI.java | 36 +- .../ui/client/highlighter/Highlight.java | 4 +- .../client/highlighter/resource/highlight.js | 835 ++++++++++++++++++ .../ui/client/places/PlaceHandler.java | 20 +- .../client/places/PlaceHistoryMapperImpl.java | 2 +- .../lumongo/ui/client/places/QueryPlace.java | 65 ++ .../lumongo/ui/client/places/SearchPlace.java | 55 -- .../org/lumongo/ui/client/widgets/Header.java | 38 +- .../lumongo/ui/client/widgets/HomeView.java | 100 +-- .../lumongo/ui/server/UIQueryServiceImpl.java | 14 +- .../org/lumongo/ui/shared/InstanceInfo.java | 59 +- 11 files changed, 1069 insertions(+), 159 deletions(-) create mode 100644 lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/resource/highlight.js create mode 100644 lumongo-ui/src/main/java/org/lumongo/ui/client/places/QueryPlace.java delete mode 100644 lumongo-ui/src/main/java/org/lumongo/ui/client/places/SearchPlace.java diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/LumongoUI.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/LumongoUI.java index 87c361d0..4a8a9440 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/client/LumongoUI.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/LumongoUI.java @@ -1,11 +1,8 @@ package org.lumongo.ui.client; import com.google.gwt.core.client.EntryPoint; -import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; -import gwt.material.design.client.ui.MaterialHeader; import gwt.material.design.client.ui.html.Div; import gwt.material.design.client.ui.html.Main; import org.lumongo.ui.client.bundle.MainResources; @@ -22,15 +19,14 @@ */ public class LumongoUI implements ContentPresenter, EntryPoint { - private SimplePanel simplePanel; - private Widget footer; - private MaterialHeader header; + private Main simplePanel; + private Footer footer; + private Header header; @Override public void onModuleLoad() { MainResources.INSTANCE.mainGSS().ensureInjected(); - GWT.log("Injecting highlighter."); HighlighterInjector.inject(); HighChartsInjector highChartsInjector = new HighChartsInjector() { @Override @@ -39,11 +35,11 @@ public void onload() { Div div = new Div(); header = new Header(); - Main baseView = createBaseView(); + simplePanel = createBaseView(); footer = new Footer(); div.add(header); - div.add(baseView); + div.add(simplePanel); div.add(footer); PlaceHandler placeHandler = new PlaceHandler(LumongoUI.this); @@ -59,27 +55,25 @@ public void onload() { @Override public Main createBaseView() { - final Main mainWrapper = new Main(); - mainWrapper.setId("main-wrapper"); - mainWrapper.setMarginTop(40); - mainWrapper.setMarginBottom(40); + simplePanel = new Main(); + simplePanel.setId("main-wrapper"); + simplePanel.setMarginTop(40); + simplePanel.setMarginBottom(40); Highcharts.setExportUrl(""); - simplePanel = new SimplePanel(); - simplePanel.getElement().setId("contentContainer"); - - mainWrapper.add(simplePanel); - - return mainWrapper; + return simplePanel; } @Override public void setContent(Widget content) { - simplePanel.setWidget(content); + simplePanel.clear(); + if (content != null) { + simplePanel.add(content); + } } - public MaterialHeader getHeader() { + public Header getHeader() { return header; } } diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/Highlight.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/Highlight.java index 47695238..404d3cad 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/Highlight.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/Highlight.java @@ -9,7 +9,7 @@ * Created by Payam Meyer on 3/14/17. * @author pmeyer */ -@JsType(namespace = JsPackage.GLOBAL, isNative = true) +@JsType(namespace = JsPackage.GLOBAL, isNative = true, name = "hljs") public class Highlight { @JsMethod @@ -19,6 +19,6 @@ public class Highlight { public native void initHighlightingOnLoad(); @JsMethod - public static native void e(Element block); + public static native void highlightBlock(Element block); } diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/resource/highlight.js b/lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/resource/highlight.js new file mode 100644 index 00000000..e455f7f2 --- /dev/null +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/highlighter/resource/highlight.js @@ -0,0 +1,835 @@ +/* + Syntax highlighting with language autodetection. + https://highlightjs.org/ + */ + +(function (factory) { + + // Find the global object for export to both the browser and web workers. + var globalObject = typeof window === 'object' && window || + typeof self === 'object' && self; + + // Setup highlight.js for different environments. First is Node.js or + // CommonJS. + if (typeof exports !== 'undefined') { + factory(exports); + } else if (globalObject) { + // Export hljs globally even when using AMD for cases when this script + // is loaded with others that may still expect a global hljs. + globalObject.hljs = factory({}); + + // Finally register the global hljs with AMD. + if (typeof define === 'function' && define.amd) { + define([], function () { + return globalObject.hljs; + }); + } + } + +}(function (hljs) { + // Convenience variables for build-in objects + var ArrayProto = [], + objectKeys = Object.keys; + + // Global internal variables used within the highlight.js library. + var languages = {}, + aliases = {}; + + // Regular expressions used throughout the highlight.js library. + var noHighlightRe = /^(no-?highlight|plain|text)$/i, + languagePrefixRe = /\blang(?:uage)?-([\w-]+)\b/i, + fixMarkupRe = /((^(<[^>]+>|\t|)+|(?:\n)))/gm; + + var spanEndTag = ''; + + // Global options used when within external APIs. This is modified when + // calling the `hljs.configure` function. + var options = { + classPrefix: 'hljs-', + tabReplace: null, + useBR: false, + languages: undefined + }; + + // Object map that is used to escape some common HTML characters. + var escapeRegexMap = { + '&': '&', + '<': '<', + '>': '>' + }; + + /* Utility functions */ + + function escape(value) { + return value.replace(/[&<>]/gm, function (character) { + return escapeRegexMap[character]; + }); + } + + function tag(node) { + return node.nodeName.toLowerCase(); + } + + function testRe(re, lexeme) { + var match = re && re.exec(lexeme); + return match && match.index === 0; + } + + function isNotHighlighted(language) { + return noHighlightRe.test(language); + } + + function blockLanguage(block) { + var i, match, length, _class; + var classes = block.className + ' '; + + classes += block.parentNode ? block.parentNode.className : ''; + + // language-* takes precedence over non-prefixed class names. + match = languagePrefixRe.exec(classes); + if (match) { + return getLanguage(match[1]) ? match[1] : 'no-highlight'; + } + + classes = classes.split(/\s+/); + + for (i = 0, length = classes.length; i < length; i++) { + _class = classes[i] + + if (isNotHighlighted(_class) || getLanguage(_class)) { + return _class; + } + } + } + + function inherit(parent) { // inherit(parent, override_obj, override_obj, ...) + var key; + var result = {}; + var objects = Array.prototype.slice.call(arguments, 1); + + for (key in parent) + result[key] = parent[key]; + objects.forEach(function (obj) { + for (key in obj) + result[key] = obj[key]; + }); + return result; + } + + /* Stream merging */ + + function nodeStream(node) { + var result = []; + (function _nodeStream(node, offset) { + for (var child = node.firstChild; child; child = child.nextSibling) { + if (child.nodeType === 3) + offset += child.nodeValue.length; + else if (child.nodeType === 1) { + result.push({ + event: 'start', + offset: offset, + node: child + }); + offset = _nodeStream(child, offset); + // Prevent void elements from having an end tag that would actually + // double them in the output. There are more void elements in HTML + // but we list only those realistically expected in code display. + if (!tag(child).match(/br|hr|img|input/)) { + result.push({ + event: 'stop', + offset: offset, + node: child + }); + } + } + } + return offset; + })(node, 0); + return result; + } + + function mergeStreams(original, highlighted, value) { + var processed = 0; + var result = ''; + var nodeStack = []; + + function selectStream() { + if (!original.length || !highlighted.length) { + return original.length ? original : highlighted; + } + if (original[0].offset !== highlighted[0].offset) { + return (original[0].offset < highlighted[0].offset) ? original : highlighted; + } + + /* + To avoid starting the stream just before it should stop the order is + ensured that original always starts first and closes last: + + if (event1 == 'start' && event2 == 'start') + return original; + if (event1 == 'start' && event2 == 'stop') + return highlighted; + if (event1 == 'stop' && event2 == 'start') + return original; + if (event1 == 'stop' && event2 == 'stop') + return highlighted; + + ... which is collapsed to: + */ + return highlighted[0].event === 'start' ? original : highlighted; + } + + function open(node) { + function attr_str(a) { + return ' ' + a.nodeName + '="' + escape(a.value) + '"'; + } + + result += '<' + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join('') + '>'; + } + + function close(node) { + result += ''; + } + + function render(event) { + (event.event === 'start' ? open : close)(event.node); + } + + while (original.length || highlighted.length) { + var stream = selectStream(); + result += escape(value.substring(processed, stream[0].offset)); + processed = stream[0].offset; + if (stream === original) { + /* + On any opening or closing tag of the original markup we first close + the entire highlighted node stack, then render the original tag along + with all the following original tags at the same offset and then + reopen all the tags on the highlighted stack. + */ + nodeStack.reverse().forEach(close); + do { + render(stream.splice(0, 1)[0]); + stream = selectStream(); + } while (stream === original && stream.length && stream[0].offset === processed); + nodeStack.reverse().forEach(open); + } else { + if (stream[0].event === 'start') { + nodeStack.push(stream[0].node); + } else { + nodeStack.pop(); + } + render(stream.splice(0, 1)[0]); + } + } + return result + escape(value.substr(processed)); + } + + /* Initialization */ + + function expand_mode(mode) { + if (mode.variants && !mode.cached_variants) { + mode.cached_variants = mode.variants.map(function (variant) { + return inherit(mode, {variants: null}, variant); + }); + } + return mode.cached_variants || (mode.endsWithParent && [inherit(mode)]) || [mode]; + } + + function compileLanguage(language) { + + function reStr(re) { + return (re && re.source) || re; + } + + function langRe(value, global) { + return new RegExp( + reStr(value), + 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') + ); + } + + function compileMode(mode, parent) { + if (mode.compiled) + return; + mode.compiled = true; + + mode.keywords = mode.keywords || mode.beginKeywords; + if (mode.keywords) { + var compiled_keywords = {}; + + var flatten = function (className, str) { + if (language.case_insensitive) { + str = str.toLowerCase(); + } + str.split(' ').forEach(function (kw) { + var pair = kw.split('|'); + compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1]; + }); + }; + + if (typeof mode.keywords === 'string') { // string + flatten('keyword', mode.keywords); + } else { + objectKeys(mode.keywords).forEach(function (className) { + flatten(className, mode.keywords[className]); + }); + } + mode.keywords = compiled_keywords; + } + mode.lexemesRe = langRe(mode.lexemes || /\w+/, true); + + if (parent) { + if (mode.beginKeywords) { + mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')\\b'; + } + if (!mode.begin) + mode.begin = /\B|\b/; + mode.beginRe = langRe(mode.begin); + if (!mode.end && !mode.endsWithParent) + mode.end = /\B|\b/; + if (mode.end) + mode.endRe = langRe(mode.end); + mode.terminator_end = reStr(mode.end) || ''; + if (mode.endsWithParent && parent.terminator_end) + mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end; + } + if (mode.illegal) + mode.illegalRe = langRe(mode.illegal); + if (mode.relevance == null) + mode.relevance = 1; + if (!mode.contains) { + mode.contains = []; + } + mode.contains = Array.prototype.concat.apply([], mode.contains.map(function (c) { + return expand_mode(c === 'self' ? mode : c) + })); + mode.contains.forEach(function (c) { + compileMode(c, mode); + }); + + if (mode.starts) { + compileMode(mode.starts, parent); + } + + var terminators = + mode.contains.map(function (c) { + return c.beginKeywords ? '\\.?(' + c.begin + ')\\.?' : c.begin; + }) + .concat([mode.terminator_end, mode.illegal]) + .map(reStr) + .filter(Boolean); + mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : { + exec: function (/*s*/) { + return null; + } + }; + } + + compileMode(language); + } + + /* + Core highlighting function. Accepts a language name, or an alias, and a + string with the code to highlight. Returns an object with the following + properties: + + - relevance (int) + - value (an HTML string with highlighting markup) + + */ + function highlight(name, value, ignore_illegals, continuation) { + + function subMode(lexeme, mode) { + var i, length; + + for (i = 0, length = mode.contains.length; i < length; i++) { + if (testRe(mode.contains[i].beginRe, lexeme)) { + return mode.contains[i]; + } + } + } + + function endOfMode(mode, lexeme) { + if (testRe(mode.endRe, lexeme)) { + while (mode.endsParent && mode.parent) { + mode = mode.parent; + } + return mode; + } + if (mode.endsWithParent) { + return endOfMode(mode.parent, lexeme); + } + } + + function isIllegal(lexeme, mode) { + return !ignore_illegals && testRe(mode.illegalRe, lexeme); + } + + function keywordMatch(mode, match) { + var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]; + return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str]; + } + + function buildSpan(classname, insideSpan, leaveOpen, noPrefix) { + var classPrefix = noPrefix ? '' : options.classPrefix, + openSpan = ''; + + return openSpan + insideSpan + closeSpan; + } + + function processKeywords() { + var keyword_match, last_index, match, result; + + if (!top.keywords) + return escape(mode_buffer); + + result = ''; + last_index = 0; + top.lexemesRe.lastIndex = 0; + match = top.lexemesRe.exec(mode_buffer); + + while (match) { + result += escape(mode_buffer.substring(last_index, match.index)); + keyword_match = keywordMatch(top, match); + if (keyword_match) { + relevance += keyword_match[1]; + result += buildSpan(keyword_match[0], escape(match[0])); + } else { + result += escape(match[0]); + } + last_index = top.lexemesRe.lastIndex; + match = top.lexemesRe.exec(mode_buffer); + } + return result + escape(mode_buffer.substr(last_index)); + } + + function processSubLanguage() { + var explicit = typeof top.subLanguage === 'string'; + if (explicit && !languages[top.subLanguage]) { + return escape(mode_buffer); + } + + var result = explicit ? + highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]) : + highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : undefined); + + // Counting embedded language score towards the host language may be disabled + // with zeroing the containing mode relevance. Usecase in point is Markdown that + // allows XML everywhere and makes every XML snippet to have a much larger Markdown + // score. + if (top.relevance > 0) { + relevance += result.relevance; + } + if (explicit) { + continuations[top.subLanguage] = result.top; + } + return buildSpan(result.language, result.value, false, true); + } + + function processBuffer() { + result += (top.subLanguage != null ? processSubLanguage() : processKeywords()); + mode_buffer = ''; + } + + function startNewMode(mode) { + result += mode.className ? buildSpan(mode.className, '', true) : ''; + top = Object.create(mode, {parent: {value: top}}); + } + + function processLexeme(buffer, lexeme) { + + mode_buffer += buffer; + + if (lexeme == null) { + processBuffer(); + return 0; + } + + var new_mode = subMode(lexeme, top); + if (new_mode) { + if (new_mode.skip) { + mode_buffer += lexeme; + } else { + if (new_mode.excludeBegin) { + mode_buffer += lexeme; + } + processBuffer(); + if (!new_mode.returnBegin && !new_mode.excludeBegin) { + mode_buffer = lexeme; + } + } + startNewMode(new_mode, lexeme); + return new_mode.returnBegin ? 0 : lexeme.length; + } + + var end_mode = endOfMode(top, lexeme); + if (end_mode) { + var origin = top; + if (origin.skip) { + mode_buffer += lexeme; + } else { + if (!(origin.returnEnd || origin.excludeEnd)) { + mode_buffer += lexeme; + } + processBuffer(); + if (origin.excludeEnd) { + mode_buffer = lexeme; + } + } + do { + if (top.className) { + result += spanEndTag; + } + if (!top.skip) { + relevance += top.relevance; + } + top = top.parent; + } while (top !== end_mode.parent); + if (end_mode.starts) { + startNewMode(end_mode.starts, ''); + } + return origin.returnEnd ? 0 : lexeme.length; + } + + if (isIllegal(lexeme, top)) + throw new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '') + '"'); + + /* + Parser should not reach this point as all types of lexemes should be caught + earlier, but if it does due to some bug make sure it advances at least one + character forward to prevent infinite looping. + */ + mode_buffer += lexeme; + return lexeme.length || 1; + } + + var language = getLanguage(name); + if (!language) { + throw new Error('Unknown language: "' + name + '"'); + } + + compileLanguage(language); + var top = continuation || language; + var continuations = {}; // keep continuations for sub-languages + var result = '', current; + for (current = top; current !== language; current = current.parent) { + if (current.className) { + result = buildSpan(current.className, '', true) + result; + } + } + var mode_buffer = ''; + var relevance = 0; + try { + var match, count, index = 0; + while (true) { + top.terminators.lastIndex = index; + match = top.terminators.exec(value); + if (!match) + break; + count = processLexeme(value.substring(index, match.index), match[0]); + index = match.index + count; + } + processLexeme(value.substr(index)); + for (current = top; current.parent; current = current.parent) { // close dangling modes + if (current.className) { + result += spanEndTag; + } + } + return { + relevance: relevance, + value: result, + language: name, + top: top + }; + } catch (e) { + if (e.message && e.message.indexOf('Illegal') !== -1) { + return { + relevance: 0, + value: escape(value) + }; + } else { + throw e; + } + } + } + + /* + Highlighting with language detection. Accepts a string with the code to + highlight. Returns an object with the following properties: + + - language (detected language) + - relevance (int) + - value (an HTML string with highlighting markup) + - second_best (object with the same structure for second-best heuristically + detected language, may be absent) + + */ + function highlightAuto(text, languageSubset) { + languageSubset = languageSubset || options.languages || objectKeys(languages); + var result = { + relevance: 0, + value: escape(text) + }; + var second_best = result; + languageSubset.filter(getLanguage).forEach(function (name) { + var current = highlight(name, text, false); + current.language = name; + if (current.relevance > second_best.relevance) { + second_best = current; + } + if (current.relevance > result.relevance) { + second_best = result; + result = current; + } + }); + if (second_best.language) { + result.second_best = second_best; + } + return result; + } + + /* + Post-processing of the highlighted markup: + + - replace TABs with something more useful + - replace real line-breaks with '
' for non-pre containers + + */ + function fixMarkup(value) { + return !(options.tabReplace || options.useBR) + ? value + : value.replace(fixMarkupRe, function (match, p1) { + if (options.useBR && match === '\n') { + return '
'; + } else if (options.tabReplace) { + return p1.replace(/\t/g, options.tabReplace); + } + return ''; + }); + } + + function buildClassName(prevClassName, currentLang, resultLang) { + var language = currentLang ? aliases[currentLang] : resultLang, + result = [prevClassName.trim()]; + + if (!prevClassName.match(/\bhljs\b/)) { + result.push('hljs'); + } + + if (prevClassName.indexOf(language) === -1) { + result.push(language); + } + + return result.join(' ').trim(); + } + + /* + Applies highlighting to a DOM node containing code. Accepts a DOM node and + two optional parameters for fixMarkup. + */ + function highlightBlock(block) { + var node, originalStream, result, resultNode, text; + var language = blockLanguage(block); + + if (isNotHighlighted(language)) + return; + + if (options.useBR) { + node = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); + node.innerHTML = block.innerHTML.replace(/\n/g, '').replace(//g, '\n'); + } else { + node = block; + } + text = node.textContent; + result = language ? highlight(language, text, true) : highlightAuto(text); + + originalStream = nodeStream(node); + if (originalStream.length) { + resultNode = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); + resultNode.innerHTML = result.value; + result.value = mergeStreams(originalStream, nodeStream(resultNode), text); + } + result.value = fixMarkup(result.value); + + block.innerHTML = result.value; + block.className = buildClassName(block.className, language, result.language); + block.result = { + language: result.language, + re: result.relevance + }; + if (result.second_best) { + block.second_best = { + language: result.second_best.language, + re: result.second_best.relevance + }; + } + } + + /* + Updates highlight.js global options with values passed in the form of an object. + */ + function configure(user_options) { + options = inherit(options, user_options); + } + + /* + Applies highlighting to all
..
blocks on a page. + */ + function initHighlighting() { + if (initHighlighting.called) + return; + initHighlighting.called = true; + + var blocks = document.querySelectorAll('pre code'); + ArrayProto.forEach.call(blocks, highlightBlock); + } + + /* + Attaches highlighting to the page load event. + */ + function initHighlightingOnLoad() { + addEventListener('DOMContentLoaded', initHighlighting, false); + addEventListener('load', initHighlighting, false); + } + + function registerLanguage(name, language) { + var lang = languages[name] = language(hljs); + if (lang.aliases) { + lang.aliases.forEach(function (alias) { + aliases[alias] = name; + }); + } + } + + function listLanguages() { + return objectKeys(languages); + } + + function getLanguage(name) { + name = (name || '').toLowerCase(); + return languages[name] || languages[aliases[name]]; + } + + /* Interface definition */ + + hljs.highlight = highlight; + hljs.highlightAuto = highlightAuto; + hljs.fixMarkup = fixMarkup; + hljs.highlightBlock = highlightBlock; + hljs.configure = configure; + hljs.initHighlighting = initHighlighting; + hljs.initHighlightingOnLoad = initHighlightingOnLoad; + hljs.registerLanguage = registerLanguage; + hljs.listLanguages = listLanguages; + hljs.getLanguage = getLanguage; + hljs.inherit = inherit; + + // Common regexps + hljs.IDENT_RE = '[a-zA-Z]\\w*'; + hljs.UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*'; + hljs.NUMBER_RE = '\\b\\d+(\\.\\d+)?'; + hljs.C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float + hljs.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... + hljs.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; + + // Common modes + hljs.BACKSLASH_ESCAPE = { + begin: '\\\\[\\s\\S]', relevance: 0 + }; + hljs.APOS_STRING_MODE = { + className: 'string', + begin: '\'', end: '\'', + illegal: '\\n', + contains: [hljs.BACKSLASH_ESCAPE] + }; + hljs.QUOTE_STRING_MODE = { + className: 'string', + begin: '"', end: '"', + illegal: '\\n', + contains: [hljs.BACKSLASH_ESCAPE] + }; + hljs.PHRASAL_WORDS_MODE = { + begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/ + }; + hljs.COMMENT = function (begin, end, inherits) { + var mode = hljs.inherit( + { + className: 'comment', + begin: begin, end: end, + contains: [] + }, + inherits || {} + ); + mode.contains.push(hljs.PHRASAL_WORDS_MODE); + mode.contains.push({ + className: 'doctag', + begin: '(?:TODO|FIXME|NOTE|BUG|XXX):', + relevance: 0 + }); + return mode; + }; + hljs.C_LINE_COMMENT_MODE = hljs.COMMENT('//', '$'); + hljs.C_BLOCK_COMMENT_MODE = hljs.COMMENT('/\\*', '\\*/'); + hljs.HASH_COMMENT_MODE = hljs.COMMENT('#', '$'); + hljs.NUMBER_MODE = { + className: 'number', + begin: hljs.NUMBER_RE, + relevance: 0 + }; + hljs.C_NUMBER_MODE = { + className: 'number', + begin: hljs.C_NUMBER_RE, + relevance: 0 + }; + hljs.BINARY_NUMBER_MODE = { + className: 'number', + begin: hljs.BINARY_NUMBER_RE, + relevance: 0 + }; + hljs.CSS_NUMBER_MODE = { + className: 'number', + begin: hljs.NUMBER_RE + '(' + + '%|em|ex|ch|rem' + + '|vw|vh|vmin|vmax' + + '|cm|mm|in|pt|pc|px' + + '|deg|grad|rad|turn' + + '|s|ms' + + '|Hz|kHz' + + '|dpi|dpcm|dppx' + + ')?', + relevance: 0 + }; + hljs.REGEXP_MODE = { + className: 'regexp', + begin: /\//, end: /\/[gimuy]*/, + illegal: /\n/, + contains: [ + hljs.BACKSLASH_ESCAPE, + { + begin: /\[/, end: /\]/, + relevance: 0, + contains: [hljs.BACKSLASH_ESCAPE] + } + ] + }; + hljs.TITLE_MODE = { + className: 'title', + begin: hljs.IDENT_RE, + relevance: 0 + }; + hljs.UNDERSCORE_TITLE_MODE = { + className: 'title', + begin: hljs.UNDERSCORE_IDENT_RE, + relevance: 0 + }; + hljs.METHOD_GUARD = { + // excludes method names from keyword processing + begin: '\\.\\s*' + hljs.UNDERSCORE_IDENT_RE, + relevance: 0 + }; + + return hljs; +})); \ No newline at end of file diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHandler.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHandler.java index 7786da9f..770f587c 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHandler.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHandler.java @@ -109,8 +109,8 @@ protected final void handlePlaces(Place place) { if (place instanceof HomePlace) { displayHomePlace(); } - else if (place instanceof SearchPlace) { - displaySearchPlace((SearchPlace) place); + else if (place instanceof QueryPlace) { + displayQueryPlace((QueryPlace) place); } } @@ -135,8 +135,21 @@ protected final Place getDefaultPlace() { return new HomePlace(); } - protected void displaySearchPlace(SearchPlace place) { + protected void displayQueryPlace(QueryPlace place) { + getContentPresenter().setContent(null); + if (place.getIndexName() != null) { + if (place.getQueryId() != null) { + // execute query and show the query/results page. + } + else { + // just show the query page. + } + } + else { + ToastHelper.showFailure("Invalid index name, taking you back to overview."); + MainController.get().goTo(new HomePlace()); + } } protected void displayHomePlace() { @@ -153,6 +166,7 @@ public void onFailure(Throwable caught) { public void onSuccess(InstanceInfo result) { MainController.get().getEventBus().fireEvent(new ResetSearchingEvent()); getWidgetController().getHomeView().drawSplashPage(result); + ((LumongoUI) getContentPresenter()).getHeader().setSideNavItems(result); getContentPresenter().setContent(getWidgetController().getHomeView()); } }); diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHistoryMapperImpl.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHistoryMapperImpl.java index 110cd4c5..57b4c283 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHistoryMapperImpl.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/places/PlaceHistoryMapperImpl.java @@ -16,7 +16,7 @@ public class PlaceHistoryMapperImpl implements PlaceHistoryMapper { private static final List DEFAULT_PLACES = Arrays.asList(new PlacePojo<>("home", new HomePlace.Tokenizer(), HomePlace.class), - new PlacePojo<>("search", new SearchPlace.Tokenizer(), SearchPlace.class)); + new PlacePojo<>("query", new QueryPlace.Tokenizer(), QueryPlace.class)); private Map> tokenizersByPrefix = new HashMap<>(); private Map, PlaceTokenizer> tokenizersByPlace = new HashMap<>(); diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/places/QueryPlace.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/places/QueryPlace.java new file mode 100644 index 00000000..803e3a82 --- /dev/null +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/places/QueryPlace.java @@ -0,0 +1,65 @@ +package org.lumongo.ui.client.places; + +import com.google.gwt.place.shared.Place; +import com.google.gwt.place.shared.PlaceTokenizer; +import org.lumongo.ui.client.util.UrlBuilder; +import org.lumongo.ui.client.util.UrlParamBuilder; + +import java.util.List; +import java.util.Map; + +/** + * Created by Matt Davis on 10/16/16. + * @author mdavis + */ +public class QueryPlace extends Place { + + private String indexName; + private String queryId; + + public QueryPlace(String indexName, String queryId) { + this.indexName = indexName; + this.queryId = queryId; + } + + public String getIndexName() { + return indexName; + } + + public String getQueryId() { + return queryId; + } + + @Override + public String toString() { + return "QueryPlace{" + "indexName='" + indexName + '\'' + ", queryId='" + queryId + '\'' + '}'; + } + + public static class Tokenizer implements PlaceTokenizer { + + @Override + public QueryPlace getPlace(String token) { + + Map> params = UrlBuilder.buildListParamMap(token); + String indexName = UrlBuilder.getSingleValue(params, "indexName"); + String queryId = UrlBuilder.getSingleValue(params, "queryId"); + + return new QueryPlace(indexName, queryId); + } + + @Override + public String getToken(QueryPlace place) { + UrlParamBuilder builder = new UrlParamBuilder(); + if (place.getIndexName() != null) { + builder.setParameter("indexName", place.getIndexName()); + } + if (place.getQueryId() != null) { + builder.setParameter("queryId", place.getQueryId()); + } + + return builder.buildString(); + } + + } + +} \ No newline at end of file diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/places/SearchPlace.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/places/SearchPlace.java deleted file mode 100644 index bb6236ea..00000000 --- a/lumongo-ui/src/main/java/org/lumongo/ui/client/places/SearchPlace.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.lumongo.ui.client.places; - -import com.google.gwt.place.shared.Place; -import com.google.gwt.place.shared.PlaceTokenizer; -import org.lumongo.ui.client.util.UrlBuilder; -import org.lumongo.ui.client.util.UrlParamBuilder; - -import java.util.List; -import java.util.Map; - -/** - * Created by Matt Davis on 10/16/16. - * @author mdavis - */ -public class SearchPlace extends Place { - - private String searchId; - - public SearchPlace(String searchId) { - this.searchId = searchId; - } - - public String getSearchId() { - return searchId; - } - - @Override - public String toString() { - return "SearchPlace{" + "searchId='" + searchId + '\'' + '}'; - } - - public static class Tokenizer implements PlaceTokenizer { - - @Override - public SearchPlace getPlace(String token) { - - Map> params = UrlBuilder.buildListParamMap(token); - String searchId = UrlBuilder.getSingleValue(params, "searchId"); - - return new SearchPlace(searchId); - } - - @Override - public String getToken(SearchPlace place) { - UrlParamBuilder builder = new UrlParamBuilder(); - if (place.getSearchId() != null) { - builder.setParameter("searchId", place.getSearchId()); - } - - return builder.buildString(); - } - - } - -} \ No newline at end of file diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/Header.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/Header.java index e58e9612..13ab01c5 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/Header.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/Header.java @@ -1,16 +1,28 @@ package org.lumongo.ui.client.widgets; import gwt.material.design.client.constants.Color; +import gwt.material.design.client.constants.IconType; import gwt.material.design.client.constants.NavBarType; import gwt.material.design.client.constants.SideNavType; import gwt.material.design.client.constants.TextAlign; +import gwt.material.design.client.ui.MaterialCollapsible; +import gwt.material.design.client.ui.MaterialCollapsibleBody; +import gwt.material.design.client.ui.MaterialCollapsibleHeader; +import gwt.material.design.client.ui.MaterialCollapsibleItem; import gwt.material.design.client.ui.MaterialHeader; +import gwt.material.design.client.ui.MaterialIcon; import gwt.material.design.client.ui.MaterialImage; +import gwt.material.design.client.ui.MaterialLink; import gwt.material.design.client.ui.MaterialNavBar; import gwt.material.design.client.ui.MaterialNavBrand; import gwt.material.design.client.ui.MaterialNavSection; import gwt.material.design.client.ui.MaterialSideNav; import org.lumongo.ui.client.bundle.MainResources; +import org.lumongo.ui.client.controllers.MainController; +import org.lumongo.ui.client.places.HomePlace; +import org.lumongo.ui.client.places.QueryPlace; +import org.lumongo.ui.shared.IndexInfo; +import org.lumongo.ui.shared.InstanceInfo; /** * Created by Payam Meyer on 3/10/17. @@ -19,6 +31,7 @@ public class Header extends MaterialHeader { private final MaterialSideNav sideNav; + private final MaterialCollapsibleBody materialCollapsibleBody; public Header() { @@ -38,13 +51,26 @@ public Header() { sideNav.setId("sideNav"); sideNav.setBackgroundColor(Color.GREY_LIGHTEN_3); sideNav.setCloseOnClick(false); - sideNav.hide(); MaterialNavBrand navBrand = new MaterialNavBrand(); navBrand.add(new MaterialImage(MainResources.INSTANCE.logoLong())); navBrand.setHref("#"); navBrand.addClickHandler(clickEvent -> sideNav.hide()); sideNav.add(navBrand); + MaterialCollapsible collapsible = new MaterialCollapsible(); + MaterialCollapsibleItem materialCollapsibleItem = new MaterialCollapsibleItem(); + MaterialCollapsibleHeader collapsibleHeader = new MaterialCollapsibleHeader(); + materialCollapsibleBody = new MaterialCollapsibleBody(); + materialCollapsibleItem.add(collapsibleHeader); + materialCollapsibleItem.add(materialCollapsibleBody); + collapsible.add(materialCollapsibleItem); + MaterialLink overView = new MaterialLink("Overview", new MaterialIcon(IconType.INFO)); + overView.addClickHandler(clickEvent -> MainController.get().goTo(new HomePlace())); + MaterialLink indexes = new MaterialLink("Indexes", new MaterialIcon(IconType.SEARCH)); + collapsibleHeader.add(indexes); + sideNav.add(overView); + sideNav.add(collapsible); + add(navBar); add(sideNav); @@ -53,4 +79,14 @@ public Header() { public MaterialSideNav getSideNav() { return sideNav; } + + public void setSideNavItems(InstanceInfo sideNavItems) { + materialCollapsibleBody.clear(); + + for (IndexInfo indexInfo : sideNavItems.getIndexes()) { + MaterialLink indexLink = new MaterialLink(indexInfo.getName()); + indexLink.addClickHandler(clickEvent -> MainController.get().goTo(new QueryPlace(indexInfo.getName(), null))); + materialCollapsibleBody.add(indexLink); + } + } } diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/HomeView.java b/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/HomeView.java index f4211d2e..155e6387 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/HomeView.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/client/widgets/HomeView.java @@ -1,12 +1,11 @@ package org.lumongo.ui.client.widgets; -import gwt.material.design.client.constants.Color; +import com.google.gwt.core.client.Scheduler; import gwt.material.design.client.ui.MaterialCard; import gwt.material.design.client.ui.MaterialCardContent; import gwt.material.design.client.ui.MaterialCardTitle; import gwt.material.design.client.ui.MaterialColumn; import gwt.material.design.client.ui.MaterialLabel; -import gwt.material.design.client.ui.MaterialPanel; import gwt.material.design.client.ui.MaterialRow; import gwt.material.design.client.ui.html.Div; import org.lumongo.ui.client.charting.Highcharts; @@ -27,61 +26,56 @@ public class HomeView extends Div { public void drawSplashPage(InstanceInfo instanceInfo) { clear(); - MaterialPanel panel = new MaterialPanel(); MaterialRow row = new MaterialRow(); - panel.add(row); - add(panel); - - MaterialColumn infoColumn = new MaterialColumn(); - infoColumn.setGrid("s12 m6"); - row.add(infoColumn); - - MaterialCard infoCard = new MaterialCard(); - infoCard.setBackgroundColor(Color.GREY_DARKEN_1); - infoCard.setTextColor(Color.WHITE); - MaterialCardContent infoContent = new MaterialCardContent(); - infoCard.add(infoContent); - infoColumn.add(infoCard); - - MaterialCardTitle infoCardTitle = new MaterialCardTitle(); - infoCardTitle.setText("Basic Info"); - infoContent.add(infoCardTitle); - - MaterialLabel lumongoVersionLabel = new MaterialLabel("LuMongo Version: " + instanceInfo.getLumongoVersion()); - MaterialLabel luceneVersionLabel = new MaterialLabel("Lucene Version: " + instanceInfo.getLuceneVersion()); - MaterialLabel lumongoMemoryLabel = new MaterialLabel("LuMongo Memory: " + instanceInfo.getLumongoMemory()); - MaterialLabel serverMemoryLabel = new MaterialLabel("Server Memory: " + instanceInfo.getServerMemory()); - MaterialLabel diskSpaceLabel = new MaterialLabel("Disk Space: " + instanceInfo.getDiskSize()); - - infoContent.add(lumongoVersionLabel); - infoContent.add(luceneVersionLabel); - infoContent.add(lumongoMemoryLabel); - infoContent.add(serverMemoryLabel); - infoContent.add(diskSpaceLabel); - - MaterialColumn chartColumn = new MaterialColumn(); - chartColumn.setGrid("s12 m6"); - Map data = new HashMap<>(); - for (IndexInfo indexInfo : instanceInfo.getIndexes()) { - data.put(indexInfo.getName(), indexInfo.getTotalDocs()); + add(row); + + { + MaterialColumn infoColumn = new MaterialColumn(); + infoColumn.setGrid("s12 m6"); + row.add(infoColumn); + + MaterialCard infoCard = new MaterialCard(); + MaterialCardContent infoContent = new MaterialCardContent(); + infoCard.add(infoContent); + infoColumn.add(infoCard); + + MaterialCardTitle infoCardTitle = new MaterialCardTitle(); + infoCardTitle.setText("Basic Info"); + infoContent.add(infoCardTitle); + + MaterialLabel lumongoVersionLabel = new MaterialLabel("LuMongo Version: " + instanceInfo.getLumongoVersion()); + MaterialLabel luceneVersionLabel = new MaterialLabel("Lucene Version: " + instanceInfo.getLuceneVersion()); + MaterialLabel lumongoMemoryLabel = new MaterialLabel("LuMongo Memory: " + instanceInfo.getLumongoMemory()); + MaterialLabel jvmFreeMemoryLabel = new MaterialLabel("JVM Free Memory: " + instanceInfo.getJvmFreeMemory()); + MaterialLabel jvmMaxMemoryLabel = new MaterialLabel("JVM Max Memory: " + instanceInfo.getJvmMaxMemoryMB()); + MaterialLabel jvmTotalMemoryLabel = new MaterialLabel("JVM Total Memory: " + instanceInfo.getJvmTotalMemoryMB()); + MaterialLabel jvmUsedMemoryLabel = new MaterialLabel("JVM Used Memory: " + instanceInfo.getJvmUsedMemory()); + + infoContent.add(lumongoVersionLabel); + infoContent.add(luceneVersionLabel); + infoContent.add(lumongoMemoryLabel); + infoContent.add(jvmFreeMemoryLabel); + infoContent.add(jvmMaxMemoryLabel); + infoContent.add(jvmTotalMemoryLabel); + infoContent.add(jvmUsedMemoryLabel); } - Highcharts chart = PieChart.getBuilder().setChartTitle("Index Info").setHeight(400).setData(data).setYAxisAllowDecimals(false).build(); - chartColumn.add(chart); - row.add(chartColumn); - - Div div = new Div(); - div.add(new MaterialLabel("{ \n" + " domain: \"127.0.0.1\",\n" + " https: false,\n" + " localUsers: true,\n" + " disableSAML: true,\n" - + " userCollection: \"local\",\n" + " exportUrl: \"http://localhost:9999/dataprocessing/export\",\n" - + " portfolioUrl: \"http://localhost:9999/dataprocessing/portfolio\",\n" - + " downloadUrl: \"http://localhost:9999/dataprocessing/download\",\n" - + " downloadChartsUrl: \"http://localhost:9999/dataprocessing/charts\",\n" + " lingoBaseUrl: \"https://admin.lexicalintelligence.com/\"\n" - + "}\n")); - row.add(div); + { + MaterialColumn chartColumn = new MaterialColumn(12, 6, 6); + MaterialCard card = new MaterialCard(); + + Map data = new HashMap<>(); + for (IndexInfo indexInfo : instanceInfo.getIndexes()) { + data.put(indexInfo.getName(), indexInfo.getTotalDocs()); + } + Scheduler.get().scheduleDeferred(() -> { + Highcharts chart = PieChart.getBuilder().setChartTitle("Index Info").setHeight(400).setData(data).setYAxisAllowDecimals(false).build(); + card.add(chart); + chartColumn.add(card); + row.add(chartColumn); + }); - //Highlight.e(div.getElement()); - - //HighlightBlock.f(div); + } } } diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/server/UIQueryServiceImpl.java b/lumongo-ui/src/main/java/org/lumongo/ui/server/UIQueryServiceImpl.java index 78ce143c..3cb72e78 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/server/UIQueryServiceImpl.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/server/UIQueryServiceImpl.java @@ -23,6 +23,7 @@ public class UIQueryServiceImpl extends RemoteServiceServlet implements UIQueryService { private static final Logger LOG = LoggerFactory.getLogger(UIQueryServiceImpl.class); + private static final int MB = 1024 * 1024; private LumongoWorkPool lumongoWorkPool; private String lumongoVersion; private String luceneVersion; @@ -52,9 +53,9 @@ public InstanceInfo getInstanceInfo() throws Exception { InstanceInfo instanceInfo = new InstanceInfo(); instanceInfo.setLumongoVersion(lumongoVersion); instanceInfo.setLuceneVersion(luceneVersion); - instanceInfo.setLumongoMemory(120L); GetIndexesResult indexes = lumongoWorkPool.getIndexes(); + List indexInfoList = new ArrayList<>(); for (String indexName : indexes.getIndexNames()) { IndexInfo indexInfo = new IndexInfo(); @@ -63,13 +64,18 @@ public InstanceInfo getInstanceInfo() throws Exception { indexInfo.setTotalDocs((int) lumongoWorkPool.getNumberOfDocs(indexName).getNumberOfDocs()); indexInfo.setFieldNames(new ArrayList<>(lumongoWorkPool.getFields(new GetFields(indexName)).getFieldNames())); - indexInfoList.add(indexInfo); } instanceInfo.setIndexes(indexInfoList); - instanceInfo.setServerMemory(Runtime.getRuntime().maxMemory()); - instanceInfo.setDiskSize(100L); + + Runtime runtime = Runtime.getRuntime(); + + // TODO: These need to be LuMongo's not this app, who cares about this app? + instanceInfo.setJvmUsedMemory((runtime.totalMemory() - runtime.freeMemory()) / MB); + instanceInfo.setJvmFreeMemory(runtime.freeMemory() / MB); + instanceInfo.setJvmTotalMemoryMB(runtime.totalMemory() / MB); + instanceInfo.setJvmMaxMemoryMB(runtime.maxMemory() / MB); return instanceInfo; } diff --git a/lumongo-ui/src/main/java/org/lumongo/ui/shared/InstanceInfo.java b/lumongo-ui/src/main/java/org/lumongo/ui/shared/InstanceInfo.java index a900701a..83336210 100644 --- a/lumongo-ui/src/main/java/org/lumongo/ui/shared/InstanceInfo.java +++ b/lumongo-ui/src/main/java/org/lumongo/ui/shared/InstanceInfo.java @@ -14,9 +14,12 @@ public class InstanceInfo implements IsSerializable { private List indexes = Collections.emptyList(); private String lumongoVersion; private String luceneVersion; - private Long serverMemory; - private Long lumongoMemory; - private Long diskSize; + private long lumongoMemory; + private long diskSize; + private long jvmUsedMemory; + private long jvmFreeMemory; + private long jvmTotalMemoryMB; + private long jvmMaxMemoryMB; public List getIndexes() { return indexes; @@ -42,33 +45,51 @@ public void setLuceneVersion(String luceneVersion) { this.luceneVersion = luceneVersion; } - public Long getServerMemory() { - return serverMemory; - } - - public void setServerMemory(Long serverMemory) { - this.serverMemory = serverMemory; - } - - public Long getLumongoMemory() { + public long getLumongoMemory() { return lumongoMemory; } - public void setLumongoMemory(Long lumongoMemory) { + public void setLumongoMemory(long lumongoMemory) { this.lumongoMemory = lumongoMemory; } - public Long getDiskSize() { + public long getDiskSize() { return diskSize; } - public void setDiskSize(Long diskSize) { + public void setDiskSize(long diskSize) { this.diskSize = diskSize; } - @Override - public String toString() { - return "InstanceInfo{" + "indexes=" + indexes + ", lumongoVersion='" + lumongoVersion + '\'' + ", luceneVersion='" + luceneVersion + '\'' - + ", serverMemory=" + serverMemory + ", lumongoMemory=" + lumongoMemory + ", diskSize=" + diskSize + '}'; + public void setJvmUsedMemory(long jvmUsedMemory) { + this.jvmUsedMemory = jvmUsedMemory; + } + + public long getJvmUsedMemory() { + return jvmUsedMemory; + } + + public void setJvmFreeMemory(long jvmFreeMemory) { + this.jvmFreeMemory = jvmFreeMemory; + } + + public long getJvmFreeMemory() { + return jvmFreeMemory; + } + + public void setJvmTotalMemoryMB(long jvmTotalMemoryMB) { + this.jvmTotalMemoryMB = jvmTotalMemoryMB; + } + + public long getJvmTotalMemoryMB() { + return jvmTotalMemoryMB; + } + + public void setJvmMaxMemoryMB(long jvmMaxMemoryMB) { + this.jvmMaxMemoryMB = jvmMaxMemoryMB; + } + + public long getJvmMaxMemoryMB() { + return jvmMaxMemoryMB; } }