org.glassfish.flashlight.datatree.impl.AbstractTreeNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2009-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2018] Payara Foundation and/or affiliates
package org.glassfish.flashlight.datatree.impl;
import static com.sun.enterprise.util.StringUtils.ok;
import com.sun.enterprise.util.ObjectAnalyzer;
import static com.sun.enterprise.util.SystemPropertyConstants.MONDOT;
import static com.sun.enterprise.util.SystemPropertyConstants.SLASH;
import org.glassfish.flashlight.datatree.TreeNode;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author Harpreet Singh
* @author Byron Nevins
* 12/18/2010 -- Added encode/decode. Note that the encoded form for a dot is
* NOT something like "\\." -- there is too much code around making assumptions
* about dots, splitting strings, etc. So we replace with ___MONDOT___
*/
public abstract class AbstractTreeNode implements TreeNode, Comparable {
protected Map children = new ConcurrentHashMap();
// bnevins 6/25/2011 -- why is normalizedChildren static ?!?
private static Map normalizedChildren = new ConcurrentHashMap();
protected String name; // The node object itself
protected String category;
protected String description;
protected boolean enabled = false;
private static final String NAME_SEPARATOR = ".";
private static final String REGEX = "(? getChildNodes() {
return children.values();
}
/**
* Returns a mutable view of the children
* @return
*/
@Override
public Collection getEnabledChildNodes() {
List childNodes = new ArrayList();
for (TreeNode child : children.values()) {
if (child.isEnabled())
childNodes.add(child);
}
return childNodes;
}
public Enumeration getChildNodesImmutable() {
return ((ConcurrentHashMap) children).elements();
}
@Override
public boolean hasChildNodes() {
return !children.isEmpty();
}
@Override
public void removeChild(TreeNode oldChild) {
String child = oldChild.getName();
if (child != null) {
children.remove(child);
}
// too fragile to hunt for the matching key...
Iterator it = normalizedChildren.values().iterator();
while (it.hasNext()) {
if (it.next() == oldChild) {
it.remove();
break;
}
}
}
@Override
public String getCategory() {
return category;
}
@Override
public void setCategory(String category) {
this.category = category;
}
@Override
public String getDescription() {
return this.description;
}
@Override
public void setDescription(String description) {
this.description = description;
}
// Byron Nevins 6/25/11
// JIRA 15964 -- what happened was that the childName was "xxx.yyy"
// there was a node that actually matched but it was not found because
// its name was "xxx\.yyy"
@Override
public TreeNode getChild(String childName) {
if (childName == null)
return null;
childName = normalizeDots(childName);
Set> entries = children.entrySet();
for (Map.Entry entry : entries) {
String entryKey = entry.getKey();
String normalizedEntryKey = normalizeDots(entryKey);
if (childName.equals(entryKey) || childName.equals(normalizedEntryKey))
return entry.getValue();
}
return null;
}
@Override
public TreeNode getNode(String completeName) {
if (completeName == null) {
return null;
}
completeName = encodePath(completeName);
Pattern pattern = Pattern.compile(AbstractTreeNode.REGEX);
String[] tokens = pattern.split(completeName);
TreeNode n = findNodeInTree(tokens);
if (n == null)
n = findNodeInTreeNormalized(completeName);
return n;
}
// confused? That's expected! This should be refactored/re-done for 3.2
// we store dots and slashes encoded. THe tokens coming in to this method
// are encoded. That's because there is lots of other code scattered around
// that looks for these special meta-characters. To be safe I'm storing them
// in the node encoded.
// But the "children" object has keys that come from the getName() of the node
// which is the value.
private TreeNode findNodeInTree(String[] tokens) {
if (tokens == null) {
return null;
}
TreeNode child = getChild(tokens[0]);
if (child == null)
child = getChild(decodeName(tokens[0]));
if (child == null)
return null;
if (tokens.length > 1)
child = ((AbstractTreeNode) child).findNodeInTree(dropFirstStringToken(tokens));
return child;
}
private String[] dropFirstStringToken(String[] token) {
if (token.length == 0 || token.length == 1) {
return null;
}
String[] newToken = new String[token.length - 1];
for (int i = 0; i < newToken.length; i++) {
newToken[i] = token[i + 1];
}
return newToken;
}
/**
* Returns all the nodes under the current tree
* @return List of all nodes in the current tree
*/
@Override
public List traverse(boolean ignoreDisabled) {
List list = new ArrayList();
if (ignoreDisabled && !this.enabled) {
return list;
}
list.add(this);
if (!hasChildNodes()) {
return list;
}
Collection childList = children.values();
for (TreeNode node : childList) {
list.addAll(node.traverse(ignoreDisabled));
}
return list;
}
@Override
public List getNodes(String pattern, boolean ignoreDisabled, boolean gfv2Compatible) {
pattern = pattern.replace("\\.", "\\\\\\."); // \. goes to \\\.
// bnevins October 2010
// design gotcha -- It used to be IMPOSSIBLE to tell the difference between
// a literal slash in a name and a slash used as a delimiter. Deep down
// under dozens of calls in the stack -- Strings are concatanated together
// Simple solution is to replace literal slashes with a token. The probe
// provider code needs to do that. jndi names are an example of this.
// Here we replace slash in the given pattern with the token to pull out
// the right stuff.
// This is a ARCHITECTURE flaw. This hack can be replaced with an
// ARCHITECTURAL fix later if desired.
List list = getNodesInternal(pattern, ignoreDisabled, gfv2Compatible);
if (list.isEmpty()) {
list = getNodesInternal(pattern.replace("/", SLASH), ignoreDisabled, gfv2Compatible);
}
if (list.isEmpty()){
list = getNodesInternal(decodeNameToDots(pattern), ignoreDisabled, gfv2Compatible);
}
return list;
}
private List getNodesInternal(String pattern, boolean ignoreDisabled, boolean gfv2Compatible) {
List regexMatchedTree = new ArrayList();
try {
if (gfv2Compatible)
pattern = convertGFv2PatternToRegex(pattern);
Pattern mPattern = Pattern.compile(pattern);
List completeTree = traverse(ignoreDisabled);
for (TreeNode node : completeTree) {
String path = node.getCompletePathName();
String path2 = null;
if (path.contains("\\")){
path2 = path.replace("\\", "");
}
Matcher matcher = mPattern.matcher(path);
if (matcher.matches()) {
regexMatchedTree.add(node);
} else if (path2 != null) {
Matcher matcher2 = mPattern.matcher(path2);
if (matcher2.matches()) {
regexMatchedTree.add(node);
}
}
}
} catch (java.util.regex.PatternSyntaxException e) {
// log this
Logger.getLogger("FLASHLIGHT-TREENODE").log(Level.FINEST, "Unable to process Regex", e);
}
return regexMatchedTree;
}
@Override
public List getNodes(String pattern) {
return getNodes(pattern, true, true);
}
private String convertGFv2PatternToRegex(String pattern) {
if (pattern.equals(STAR)) {
return ".*";
}
// Doing this intermediate step as replacing "*" in a pattern with ".*"
// is too hassling
String modifiedPattern = pattern.replaceAll("\\*", ":");
return modifiedPattern.replaceAll(":", ".*");
}
@Override
public int compareTo(TreeNode other) {
return getName().compareTo(other.getName());
}
@Override
public boolean equals(Object o) {
if(this == o)
return true;
// don't waste time checking o for null
// if instanceof's first param is null it will return false
// see JLS, 15.19.2
if(!(o instanceof AbstractTreeNode))
return false;
// guaranteed to not throw an Exception because of above!
AbstractTreeNode other = (AbstractTreeNode) o;
// work hard to avoid NPE's !!
return (name == null ? other.name == null : name.equals(other.name))
&& (category == null ? other.category == null : category.equals(other.category))
&& (description == null ? other.description == null : description.equals(other.description));
}
@Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 23 * hash + (this.category != null ? this.category.hashCode() : 0);
hash = 23 * hash + (this.description != null ? this.description.hashCode() : 0);
return hash;
}
@Override
public TreeNode getPossibleParentNode(String pattern) {
// simplify by bailing out early if preconditions are not met...
if (pattern == null || pattern.length() <= 0 || pattern.indexOf('*') >= 0)
return null;
TreeNode node = null;
int longest = 0;
for (TreeNode n : traverse(true)) {
String aname = n.getCompletePathName();
if (aname == null)
continue; // defensive pgming
// JIRA 15500 -- there may be a backslash in the name!
if (pattern.startsWith(aname) || pattern.startsWith(aname.replace("\\", ""))) {
int thisLength = aname.length();
// keep the longest match ONLY!
if (node == null || thisLength > longest) {
node = n;
longest = thisLength;
}
}
}
return node;
}
private String encodeNodeName(String nodeName) {
// The order is important!!
return nodeName.replace("\\.", MONDOT).replace(".", MONDOT).replace("\\/", SLASH).replace("/", SLASH);
}
private String encodePath(String thePath) {
// REST encodes (1) to (2)
// aaa bbb.x cccc
// aaa.bbb\\x.cccc
// we want aaa.bbb___MONDOT___x.cccc
return thePath.replace("\\/", SLASH).replace("\\.", MONDOT);
}
// todo replace with \\. ???
private String decodeName() {
return decodeName(name);
}
private static String decodeName(String s) {
return s.replace(SLASH, "/").replace(MONDOT, "\\.");
}
private static String decodeNameToDots(String s) {
return s.replace(SLASH, ".").replace(MONDOT, ".").replace("\\/", ".").replace("\\.", ".").replace('/', '.');
}
private TreeNode findNodeInTreeNormalized(String desiredName) {
// this is ONLY called when there is no match using the tools prior to 2/10/11
// so the performance hit should be reasonable
if (!ok(desiredName))
return null;
desiredName = decodeNameToDots(desiredName);
TreeNode node = normalizedChildren.get(desiredName);
// one more try. GUI sometimes chops off the starting "server".
if (node == null && !desiredName.startsWith("server.")) {
node = normalizedChildren.get("server." + desiredName);
// one final try. The string might be "instance1.blah" -- we must change that
// to "instance1.server.blah"
if (node == null && (desiredName = insertServerString(desiredName)) != null)
node = normalizedChildren.get(desiredName);
}
return node;
}
private static String insertServerString(String desiredName) {
try {
String[] bits = desiredName.split("\\.");
if (bits.length >= 2) {
if (bits[1].equals("server"))
return desiredName;
StringBuilder sb = new StringBuilder(bits[0]);
sb.append(".server");
for (int i = 1; i < bits.length; i++) {
sb.append('.').append(bits[i]);
}
return sb.toString();
}
}
catch (Exception e) {
// fall through
}
return null;
}
private static String normalizeDots(String s) {
// Normalized means a literal dot is "\." (one 'real' backslash followed by dot
// in a java string in source code it would be: "\\." -- where one of the backslashes isn't 'real'
if(s == null || !hasDots(s)) // return quick for performance
return s;
// to avoid expensive regexp work I do it this way...
// it changes all occurences of "." with no preceding '\' with "\." and all
// MONDOTS also.
// I.e. all dots leave here with a backslash in front of them!
return s.replace("\\.", MONDOT).replace(".", MONDOT).replace(MONDOT, "\\.");
}
private static boolean hasDots(String s) {
if(s == null) {
return false;
}
if(s.contains(MONDOT)) {
return true;
}
return s.contains(".");
}
}