IdUtils.java
/*
** Module : IdUtils.java
** Abstract : A set of useful static routines to handle node IDs.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 SIY 20050303 @20360 Created initial version
** 002 SIY 20050328 @20594 Fixed comments.
** 003 SIY 20050421 @21013 Added careful name validness check.
** 004 SIY 20050505 @21194 Added method upcaseFirst(). Moved splitId()
** from RamRemapper.
** 005 GES 20060304 @24889 Added '.' as a valid character in a node id.
** 006 SIY 20090712 @43136 Refactoring.
** 007 ECF 20150715 Replace StringBuffer with StringBuilder.
** 008 SP 20250416 Removed toLowerCase(). Added upper case letters as valid chars.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.directory;
import java.util.*;
/**
* A set of useful static routines to handle node and other IDs.
*
* @author SIY
*/
public class IdUtils
{
/** Characters allowed in the node name. */
static final String valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.0123456789";
/**
* Generate a list of partial paths starting from root to specified node
* ID. For example, <code>nodeId</code> <b>/security/users/siy </b> will
* produce following set: <br>
* <b>/security </b> <br>
* <b>/security/users </b> <br>
* <b>/security/users/siy </b> <br>
*
* @param nodeId
* Node ID to normalize.
*
* @return An array of normalized node IDs or <code>null</code> if node
* ID is invalid.
*/
public static String[] listPaths(String nodeId)
{
if (nodeId == null)
{
return null;
}
String[] list = nodeId.split("/", 0);
if (list == null || list.length == 0)
{
return null;
}
if (!list[0].isEmpty())
{
return null;
}
List<String> result = new ArrayList<>();
StringBuilder res = new StringBuilder(nodeId.length() + 1);
for (int i = 0; i < list.length; i++)
{
if (list[i].isEmpty())
{
continue;
}
res.append("/");
res.append(list[i]);
result.add(res.toString());
}
return result.toArray(new String[result.size()]);
}
/**
* This method check nodeId validness and returns it in normalized form
* <code>/elem0/elem2/.../elemN</code>. All redundant
* forward slashes are removed. If leading slash is omitted then path is
* assumed invalid.
*
* @param nodeId
* Node ID to normalize.
*
* @return Normalized node ID or <code>null</code> if node ID is invalid.
*/
public static String normalize(String nodeId)
{
if (nodeId == null)
{
return null;
}
//Special case
if (nodeId.isEmpty())
{
return "";
}
String[] list = nodeId.split("/", 0);
if (list == null || list.length == 0)
{
return null;
}
if (!list[0].isEmpty())
{
//No leading slash, relative paths are not allowed.
return null;
}
StringBuilder res = new StringBuilder(nodeId.length() + 1);
for (int i = 0; i < list.length; i++)
{
String link = list[i];
if (link.isEmpty()) //Intermediate double slash, ignore
{
continue;
}
if (!verifyChars(link) || link.length() > 256)
{
return null;
}
res.append("/");
res.append(link);
}
return res.toString();
}
/**
* Uppercase first letter in the given string. If string contains dashes,
* they are removed and each new part after dash is changed to upper case.
* <p>
* For example: <br>
* <b>xml </b> will be converted to <b>Xml </b> <br>
* <b>xml-ldap </b> will be converted to <b>XmlLdap </b> <br>
* <b>xml-ldap-sql </b> will be converted to <b>XmlLdapSql </b> <br>
*
* @param str
* Source string.
*
* @return Source string with first character converted to the upper case.
*/
public static String upcaseFirst(String str)
{
if (str.indexOf('-') > 0)
{
String[] s = str.split("\\-", 2);
return upcaseFirst(s[0]) + upcaseFirst(s[1]);
}
return str.toUpperCase().charAt(0) + str.substring(1);
}
/**
* Split path into parent id and node name. Note that for root node it
* returns <code>null</code> since there is no parent for the root node.
*
* @param id
* Node id.
*
* @return Array with two elements, first contains id of root node, second
* contains node id. If something is wrong with path then
* <code>null</code> is returned.
*/
static String[] splitId(String id)
{
id = IdUtils.normalize(id);
if (id == null)
return null;
int pos = id.lastIndexOf("/");
if (pos < 0)
return null;
return new String[] {id.substring(0, pos), id.substring(pos + 1)} ;
}
/**
* Validate string content.
*
* @param string
* Source string.
*
* @return <code>true</code> if string contains only allowed characters
* and <code>false</code> otherwise.
*/
private static boolean verifyChars(String string)
{
if (string == null)
return false;
for (int i = 0; i < string.length(); i++)
if (valid.indexOf(string.charAt(i)) < 0)
return false;
return true;
}
}