All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.linkedin.restli.internal.common.PathSegment Maven / Gradle / Ivy

Go to download

Pegasus is a framework for building robust, scalable service architectures using dynamic discovery and simple asychronous type-checked REST + JSON APIs.

There is a newer version: 27.7.18
Show newest version
/*
   Copyright (c) 2012 LinkedIn Corp.

   Licensed 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.
*/

package com.linkedin.restli.internal.common;

import java.util.HashMap;
import java.util.regex.Pattern;


/**
 * Name[index] representation of a path segment of a dot-delimited path referencing an
 * entry in a complex data object.
 *
 * @author adubman
 *
 */
public class PathSegment
{
  public static final String      PATH_SEPARATOR = ".";
  private static final Pattern    INDEX_PATTERN  = Pattern.compile("(^.+)\\[(\\d+)\\]$");
  private static final char[]     ENCODED_CHARS  = { '[', ']', '.' };
  public static final AsciiHexEncoding CODEC          = new AsciiHexEncoding('~', ENCODED_CHARS);
  private final String            _name;
  private final Integer           _index;

  private PathSegment(String name, Integer index) throws PathSegmentSyntaxException
  {
    // Should never happen as parse() checks that name is not empty
    assert (name != null);

    try
    {
      this._name = CODEC.decode(name);
    }
    catch (AsciiHexEncoding.CannotDecodeException e1)
    {
      throw new PathSegmentSyntaxException("Cannot decode key name " + name);
    }

    this._index = index;
  }

  /**
   * Parse a (possibly) indexed path segment out of a string. Make sure no path delimiters
   * appear in the string, as this would not be an individual path segment.
   *
   * Input string may not contains '.' char or spaces.
   *
   * @param string a string to parse.
   * @return an PathSegment object.
   * @throws PathSegmentSyntaxException
   *           if invalid index value or key segment delimiter appears in the input
   *           string.
   */
  public static PathSegment parse(String string) throws PathSegmentSyntaxException
  {
    if (string == null || string.isEmpty())
    {
      return null;
    }

    if(string.charAt(string.length()-1) == ']')
    {
      int startBraceIdx = string.lastIndexOf('[');
      if(startBraceIdx <= 0)
      {
        if(startBraceIdx == -1)
          throw new PathSegmentSyntaxException("Path segment parsing error.  '[' expected but not found: " + string);
        else
          throw new PathSegmentSyntaxException("Path segment parsing error.  Path segment name expected before '[' but not found: " + string);
      }

      String name = string.substring(0, startBraceIdx);
      String indexStr = string.substring(startBraceIdx+1, string.length()-1);

      try
      {
        int index = Integer.parseInt(indexStr);
        if(index < 0)
        {
          throw new PathSegmentSyntaxException("Path segment parsing error.  Negative integer key index not allowed: " + string);
        }
        return new PathSegment(name, index);
      }
      catch (NumberFormatException e)
      {
        throw new PathSegmentSyntaxException("Path segment parsing error. Invalid Non-integer key index:  " + string);
      }
    }
    else
    {
      return new PathSegment(string, null); // Not an indexed key
    }
  }

  /**
   * @return the name
   */
  public String getName()
  {
    return _name;
  }

  /**
   * @return the index
   */
  public Integer getIndex()
  {
    return _index;
  }

  @Override
  public String toString()
  {
    StringBuilder sb = new StringBuilder(_name);

    if (_index != null)
      sb.append("[").append(_index).append("]");

    return sb.toString();
  }

  /**
   * Stores this object in the provided Map. In case of an indexed key use _name to
   * get a corresponding List, and _index for the element in the list. In case of
   * a simple key just put the value at the name key
   *
   * No overrides are allowed.
   * @param map the map to store the value in
   * @param value the value to put in the map
   * @throws PathSegmentSyntaxException if the object is unable to be placed
   */
  public void putOnDataMap(MapMap map, String value) throws PathSegmentSyntaxException
  {
    // If not an indexed key, just store the value in the provided Map
    // with the current _name as key. If already there - error
    if (_index == null)
    {
      if (map.get(_name) != null)
        throw new PathSegmentSyntaxException("Duplicate references to key: " + _name);

      map.put(_name, value);
      return;
    }

    // Otherwise, get the element at the key _name, which is assumed to be
    // a Map keyed on Integer (as opposed to String for DataMaps), and store
    // the value at the index key in that map.
    ListMap listMap;
    Object entry = map.get(_name);
    if (entry == null)
    {
      listMap = new ListMap();
      map.put(_name, listMap);
    }
    else if (entry instanceof ListMap)
    {
      listMap = (ListMap)entry;
    }
    else
    {
      throw new PathSegmentSyntaxException("Conflicting references to key: " + toString());
    }

    // Now put the value on the _index entry of the list if it's not there.
    // If it is - exception
    if (listMap.get(_index) != null)
      throw new PathSegmentSyntaxException("Duplicate references to key: " + _name);

    listMap.put(_index, value);
  }

  /**
   * Get the value referenced by this path segment in the provided DataMap
   *
   * The method assumes the current key is not a leaf segment of the key path, i.e.
   * it always references a Map value in the current Map.
   *
   * @param map the Map
   * @return a Map of Strings to Maps
   * @throws PathSegmentSyntaxException if the current key is a leaf segment
   */
  public MapMap getNextLevelMap(MapMap map) throws PathSegmentSyntaxException
  {
    Object object = map.get(_name);

    if (object == null)
    {
      MapMap nextLevelDataMap = new MapMap();
      // If not an indexed path segment, create a new Map, put it at the
      // current key and return it.
      if (_index == null)
      {
        map.put(_name, nextLevelDataMap);
      }
      // If an indexed key, create a new ListMap, put it at the current _name
      // and put next level data map at the current _index in the List Map
      else
      {
        ListMap listMap = new ListMap();
        map.put(_name, listMap);
        listMap.put(_index, nextLevelDataMap);
      }
      return nextLevelDataMap;
    }

    if (_index == null)
    {
      if (object instanceof MapMap)
        return (MapMap) object;
      throw new PathSegmentSyntaxException("Conflicting references to key " + toString());
    }

    if (object instanceof ListMap)
    {
      ListMap list = (ListMap) object;

      Object object2 = list.get(_index);

      if (object2 == null)
      {
        MapMap nextLevelMap = new MapMap();
        list.put(_index, nextLevelMap);
        return nextLevelMap;
      }
      else if (object2 instanceof MapMap)
      {
        return (MapMap) object2;
      }
      else
      {
        throw new PathSegmentSyntaxException("Conflicting references to key "
            + toString());
      }
    }

    throw new PathSegmentSyntaxException("Conflicting references to key " + toString());
  }

  public static class PathSegmentSyntaxException extends Exception
  {
    private static final long serialVersionUID = 1L;

    /**
     * Initialize a PathSegmentSyntaxException based on the given message.
     *
     * @param message the exception message
     */
    public PathSegmentSyntaxException(String message)
    {
      super(message);
    }
  }

  static class ListMap extends HashMap
  {
    private static final long serialVersionUID = 1L;
  }

  static class MapMap extends HashMap
  {
    private static final long serialVersionUID = 1L;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy