/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2009-2012 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.
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.glassfish.admin.amx.core;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.management.ObjectName;
import org.glassfish.admin.amx.base.DomainRoot;
import static org.glassfish.admin.amx.core.PathnameConstants.*;
import static org.glassfish.external.amx.AMX.*;
import org.glassfish.external.arc.Stability;
import org.glassfish.external.arc.Taxonomy;
/**
Parses a pathname into parts.
<p>
The root part (leading "/") is not included in the parts list returned
by {@link #parts}.
*/
@Taxonomy(stability = Stability.UNCOMMITTED)
public final class PathnameParser
{
private static void debug(final Object o)
{
System.out.println("" + o);
}
private final char mDelim;
private final char mNameLeft;
private final char mNameRight;
private final String mPath;
private final List<PathPart> mParts;
private final boolean mIsFullPath;
public static final class PathPart
{
private final String mType, mName;
public PathPart(final String type, final String name)
{
mType = type;
mName = name;
}
public String type()
{
return mType;
}
public String name()
{
return mName;
}
public boolean isWildType()
{
return mType.indexOf(MATCH_ZERO_OR_MORE) >= 0;
}
public boolean isWildName()
{
return mName != null && mName.indexOf(MATCH_ZERO_OR_MORE) >= 0;
}
@Override
public String toString()
{
return pathPart(mType, mName);
}
}
@Override
public String toString()
{
final StringBuilder buf = new StringBuilder();
buf.append(mPath).append(" as ").append(mParts.size()).append(" parts: ");
buf.append("{");
final String delim = ", ";
for (final PathPart part : mParts)
{
buf.append(part.toString());
buf.append(delim);
}
if (!mParts.isEmpty())
{
buf.setLength(buf.length() - delim.length());
}
buf.append("}");
return buf.toString();
}
public PathnameParser(final String path)
{
this(path, SEPARATOR, SUBSCRIPT_LEFT, SUBSCRIPT_RIGHT);
}
public PathnameParser(
final String path,
final char delim,
final char nameLeft,
final char nameRight)
{
mPath = path;
mDelim = delim;
mNameLeft = nameLeft;
mNameRight = nameRight;
mParts = parse();
mIsFullPath = path.startsWith(DomainRoot.PATH);
}
public boolean isFullPath()
{
return mIsFullPath;
}
public boolean isRoot()
{
return mParts.isEmpty();
}
/** return true if any part of the path includes a wildcard */
public boolean isWild()
{
for( final PathPart part : mParts )
{
if ( part.isWildType() || part.isWildName() )
{
return true;
}
}
return false;
}
public List<PathPart> parts()
{
return mParts;
}
public String type()
{
return mParts.get(mParts.size() - 1).type();
}
public String name()
{
return mParts.get(mParts.size() - 1).name();
}
public String parentPath()
{
if (mParts.isEmpty())
{
return null;
}
final StringBuffer buf = new StringBuffer();
for (int i = 0; i < mParts.size() - 1; ++i)
{
final PathPart part = mParts.get(i);
buf.append(part.toString());
// don't want a trailing slash
if (i < mParts.size() - 2)
{
buf.append(SEPARATOR);
}
}
return DomainRoot.PATH + buf.toString();
}
/**
This pattern finds a type and whatever follows.
FIXME: how to support arbitrary delimiter or subscript?
*/
private static final Pattern TYPE_SEARCH_PATTERN = Pattern.compile(LEGAL_TYPE_PATTERN + ".*");
/**
This pattern finds a name up to the terminating SUBSCRIPT_RIGHT.
*/
private static final Pattern NAME_SEARCH_PATTERN = Pattern.compile("(" + LEGAL_NAME_PATTERN + ")" + SUBSCRIPT_RIGHT + ".*");
/* a legal type, by itself */
private static final Pattern LEGAL_TYPE_PATTERN_COMPILED = Pattern.compile( LEGAL_TYPE_PATTERN );
/* a legal name, by itself */
private static final Pattern LEGAL_NAME_PATTERN_COMPILED = Pattern.compile( LEGAL_NAME_PATTERN );
private static boolean isValidType(final String type)
{
final Matcher matcher = LEGAL_TYPE_PATTERN_COMPILED.matcher(type);
return matcher.matches();
}
private static boolean isValidName(final String type)
{
final Matcher matcher = LEGAL_NAME_PATTERN_COMPILED.matcher(type);
return matcher.matches();
}
/**
*/
private void parse(final String path, final List<PathPart> parts)
{
//debug( "PathnameParser: parsing: " + path );
if (path == null || path.length() == 0)
{
throw new IllegalArgumentException(path);
}
String remaining = path;
// strip the leading "/" for DomainRoot if present, to avoid having
// to support a type of an empty string eg ""/foo/bar
if (remaining.startsWith(DomainRoot.PATH))
{
remaining = remaining.substring(DomainRoot.PATH.length());
}
final Pattern typePattern = TYPE_SEARCH_PATTERN;
// how to know whether to escape the name-left char if it can be any char?
while (remaining.length() != 0)
{
Matcher matcher = typePattern.matcher(remaining);
if (!matcher.matches())
{
throw new IllegalArgumentException("No match: " + remaining);
}
final String type = matcher.group(1);
//debug( "PathnameParser, matched type: \"" + type + "\"" );
char matchChar;
if (type.length() < remaining.length())
{
matchChar = remaining.charAt(type.length());
remaining = remaining.substring(type.length() + 1);
}
else
{
final PathPart part = new PathPart(type, null);
parts.add(part);
break;
}
//debug( "PathnameParser, match char: \"" + matchChar + "\"" );
//debug( "PathnameParser, remaining: \"" + remaining + "\"" );
String name = null;
if (matchChar == mNameLeft)
{
// anything goes in a name, and we do NOT allow escaped SUBSCRIPT_RIGHT,
// so just scarf up everything untilthe next SUBSCRIPT_RIGHT.
final int idx = remaining.indexOf(mNameRight);
if (idx < 0)
{
throw new IllegalArgumentException(path);
}
name = remaining.substring(0, idx);
remaining = remaining.substring(idx + 1);
if (remaining.length() != 0 && remaining.charAt(0) == mDelim)
{
remaining = remaining.substring(1);
}
}
final PathPart part = new PathPart(type, name);
parts.add(part);
//debug( "PathnameParser, matched part: \"" + part + "\"" );
}
/*
String s = "";
for( final PathPart part : parts ){
s = s + "{" + part + "}";
}
debug( "FINAL PARSE for : " + path + " = " + s);
*/
}
private List<PathPart> parse()
{
final List<PathPart> parts = new ArrayList<PathPart>();
parse(mPath, parts);
return parts;
}
private static void checkName(final String name)
{
if ( name != null && ! isValidName(name) )
{
throw new IllegalArgumentException("Illegal name: " + name);
}
}
private static void checkType(final String type)
{
if (type == null)
{
throw new IllegalArgumentException("Illegal type: null");
}
if (type.indexOf(SUBSCRIPT_LEFT) >= 0 || type.indexOf(SUBSCRIPT_RIGHT) >= 0)
{
throw new IllegalArgumentException("Illegal type: " + type);
}
if (!isValidType(type))
{
throw new IllegalArgumentException("Illegal type: " + type);
}
}
public static String pathPart(final String type, final String name)
{
checkName(name);
final String namePart = (name == null) ? "" : SUBSCRIPT_LEFT + name + SUBSCRIPT_RIGHT;
final String part = pathPart(type) + namePart;
return part;
}
public static String pathPart(final String type)
{
checkType(type);
return type;
}
public static String path(final String parentPath, final String type, final String name)
{
if (parentPath != null && parentPath.length() == 0 && !type.equals(Util.deduceType(DomainRoot.class)))
{
throw new IllegalArgumentException("parent path cannot be the empty string");
}
String path = (parentPath == null || parentPath.equals(DomainRoot.PATH)) ? DomainRoot.PATH : parentPath + SEPARATOR;
path = path + pathPart(type, name);
// make sure it can be parsed
new PathnameParser(path);
return path;
}
public static String parentPath(final ObjectName objectName)
{
return Util.unquoteIfNeeded(objectName.getKeyProperty(PARENT_PATH_KEY));
}
}